Why hasn't functional programming taken over yet?
web.archive.org
web.archive.org
Which is not all it's touted up to be.
>Concurrency; Plays extremely nice with the rising multi-core technology
Nice, but not that nice. Don't expect any significant speedup for the most common kinds of programs.
>Programs are usually shorter and in some cases easier to read
For some people they are harder to read. As for shorter, Python can be pretty terse too, as can lots of other languages.
>Productivity goes up (example: Erlang)
Anecdotal. In the real world, most systems in production (including at NASA and the most critical environments) are made with imperative/oo/etc programming, which must count for something. For every WhatsApp there are 10,000 stories of such programs.
>Imperative programming is a very old paradigm (as far as I know)
Functional programming is 5+ decades old too. And parts of math are even older, but we're still keeping them...
>and possibly not suitable for the 21st century
Citation needed.
Though WhatsApp's success was predicated on dogged adherence to core principles of understanding their problem extremely well, selecting the right tools to solve that problem, and do as much as possible with as little as possible. Erlang was just one part of that. Choosing FreeBSD, not hiring craploads of engineers just for the sake of hiring, keeping as little intermediate state as possible, etc. we're all contributors to their success story.
But if you have a high-concurrency, small-packet, low-latency, supreme-uptime message processing shaped problem it's pretty dang hard to do better than Erlang as a foundational choice.
No 10,000 other WhatsApps. 10,000 success stories of programs doing what they need to do with a small team behind them and reliability.
It's about the technical/project success aspects, not about the monetary side of the equation -- not even about user count. After all Yahoo (back in the day) and Facebook had a much higher user count while being written in PHP.
> Which is not all it's touted up to be.
It's not even true!
The other side of the coin is that there is tremendous "practical momentum" in software that is collected in the mass of code written in non-FP ways. Experienced programmers are usually expected to fix the symptoms, not cure the disease. Clever people might slip in some functional ideas here and there, but you're almost always better off learning the nuts-and-bolts of integrating Lucene with your Jetty app than you are learning Clojure.
This is exactly what I've been saying... and it's the essence of the "FP non-adoption problem." I came to FP because I became frustrated with classes of bugs that appeared over and over again in OO (Ruby, in my case) but which seem to plague FP code much less. In essence, I came to reduce labor/increase productivity; it's "laziness/impatience/hubris" all over again.
One indicator that I was "naturally" coming to FP is that I noticed I was writing Ruby code in a very functional style. I say "naturally" because 1) it made it easier to test 2) it reduced spaghetti code and dependencies 3) it made it more modular 4) it reduced side effects and bug generation. And all of these worked together to just make better code, period.
Then I came across Elixir (http://elixir-lang.org/) and lo and behold, now I can't wait to find work in this shiny new FP language.
If most people in a career came to prefer a certain paradigm after 10-20 years in that career, shouldn't everyone in that career be taking a serious look at it?
There is no doubt that FP has taken over JavaScript - All new popular frameworks including Angular, Polymer and React are declarative/functional with the exception of a few minor (but very useful) imperative concepts (e.g. these frameworks do encourage you to keep 'state' inside models).
FP is awesome but 100% pure FP is just not practical - When building real-life systems, ultimately, you need to keep stack of state somewhere - Be in in the database or inside a model in your code.
Clojurescript has a lot of other problems -- e.g. not really having an ecosystem outside of Lein -- but using cljs/om for the first time was like a breath of fresh air.
For what's it worth, some people refer to Haskell as their favourite imperative language. And they are only semi-joking: while the library ecosystem is firmly in the camp of functional idioms, imperative programming is Haskell is pretty nice and straightforward---you'll just have to tag your code with eg IO or the State Monad.
Imperative sieve of Eratosthenes:
But with partial application and currying you could pass the state in at any point in the program, so all the functions on the inside are pure, while state exists on the edges.
Whether or not the state is hidden is not one of the distinguishing features of functional programming.
Referential transparency and being able to declare certain "values" to be immutable even though there are some mutable values around are. For example, if some code is referentially transparent, putting it into continuation-passing style preserves the referential transparency.
BTW, some say that being able to specify the result that you want rather than needing to specify how those results are computed -- which is sometimes called "declarative programming" -- is what makes functional programming powerful, but I disagree: it is being referentially-transparent (or more precisely, more referentially-transparent that imperative code) and giving the programmer the ability to credibly assert that this or that data structure is immutable, combined with language and library support for working with immutable data structures.
Another distinguishing feature of functional programming: being able to rely on (or hope for) certain compiler optimizations not available in imperative languages. (It is because of this feature that Scheme is considered by some to be a functional language.) Referential transparency might be necessary to make these optimizations tractable.
OOP approach hides state because it uses private variables that might be modified in the body of the function. Yet there is nothing in the type signature that states that this function is not referentially transparent!
That and everything being an expression that returns a value.
Mainstream languages are slowly moving towards more and more functional concepts. The progress towards referential transparency is slow, but ongoing.
Some algorithms are hard to express in pure, strict functional settings. (There's even an impossibility theorem for a few algorithms, but that theorem doesn't apply to pure, lazy languages.) That being said, the worst asymptotic slowdown is logarithmic---because you can always simulate random access memory.
On a more practical side, you have constant factors to worry about, and things like caches. Look at eg Don Stewarts work for how to deal with these in a functional language.
(And, in practice, there's no shame in throwing in the towel and either interfacing with other languages, or even switching to them.)
http://stackoverflow.com/questions/2835801/why-hasnt-functio...
I'm not in the valley, and I can't recall ever seeing an ad for functional anything, not even Erlang. I'm sure there's some around, around here, but not enough that I'll see it when I'm casually looking.
All this does is pull a list of "spot instance" prices from AWS EC2, sort them and filter them. I rewrote this from a python script that became too complex.
The last time I looked at Haskell, it needed ~700MB of dependencies before I could even get started.
Edit: Clojure looks like a good way to start, although https://github.com/mrowe/clj-aws-ec2 isn't really complete.
ghc is big, indeed. There are smaller alternate compilers and interpreters (that no one uses). Compiled executables are usually smaller than ghc, which is probably the biggest Haskell programs ever written.
So for example, here you could make a nice little EC2 DSL that would get your stuff (for the actual Haskellers: Free Monad):
spotPrices :: Region -> EC2 [SpotPrice]
spotPrices = GetSpotPrice -- DSL Leaf, executed by runEC2
spotPricesForAllRegions :: [Region] -> EC2 [SpotPrice]
spotPricesForRegions regions =
concat <$> MapM spotPrices regions
Your spot price update function becomes something even simpler now: runEC2 :: EC2 a -> EC2Config -> IO a
runEC2 = stuff -- this is where you call whatever EC2
-- lib is available
updateSpotPriceFile :: [Region] -> IO [SpotPrice]
updateSpotPriceFile regions = do
spotPrices <- runEC2 (spotPricesForAllRegions regions)
(myEC2Configuration)
writeToFile (toJSON spotPrices) "spotprices.json"
return spotPrices
I'm not going to port all of it, but with a more functional version you could avoid having to keep on making EC2 clients all over the place, and you could keep your config in one place.The problem is that it's pretty obtuse if you don't know the right design patterns. There's a myth in FP circles that design patterns are for OOP only ("we have functions, we don't need observer patterns!"), but it's a myth. But if you get good, you can write some pretty concise code + get all the goodies.
Haskell has the whole GHC thing, and also the issue of lack of good library documentation (or lack of libraries on certain stuff)...
Also recursive functions tend to spiral out of control and have ridiculous exponential run times.
Recursion has nothing to do with stack traces. Stack are one way to implement some version of recursion in functions.
Some other versions of recursion in functions can be implemented with loops.
And, of course, not only function can be recursive, but data structures as well. (And a few other things.) You can traverse these data structures any way you feel like.
> It's difficult to debug and follow the code around, or even to understand the code if someone else wrote it.
I have that problem with loops (and mutation) much more than with recursion. Different things make different people's heads hurt.
> Also recursive functions tend to spiral out of control and have ridiculous exponential run times.
Oh, that's an artifact of how you've been taught (or self-taught). Loops are a special case that only works well for simple tasks. Recursion is more general and can express the simple case in a simple way.
Of course, if you only reach for recursion in the complicated cases, all uses of recursion you see will be complicated. Simple observation bias.
As someone who also has visited that dark and twisted land... I completely agree with you (every post in this thread).
I don't think I ever once experienced the supposed benefits.
If you have exponential run times, I don't think tail call optimization would help you.
Of course, that claim with the exponential run time in the first place is ludicrous: sure, you'll have a hard time implementing the Ackermann function with loops. But that's a problem with loops, not with function calls.
I say function calls here, because recursion in function is just a special case of function calls. You gotta have functions in a modern language in any case, so recursion comes for free. No need to introduce another concept like loops and clutter up your language.
I also disagree wrt it being more difficult to debug, understand, etc. Your mileage may vary here.
Second, functional programming usually emphasises combinators like map, filter, reduce/foldr. Those are easier to read than either naked recursion or loops.
In theory. In the actual world, loops are how we're used to think about doing N things.
For anyone else---normal people without programming experience, or programmers trained in different paradigms---loops are not special.
Of course, we mostly talk about programmers here, and imperatively trained programmers are in the majority.
In any case, lots of languages have gotten map and filter, and people seem to adapt to them just fine. We just have to wait another decade for the more interesting combinators to trickle down.
No, we refers to both "imperatively trained programmers", and, more especially, laymen.
>For anyone else---normal people without programming experience, or programmers trained in different paradigms---loops are not special
I very much doubt any layman who does anything repetitive (e.g. runs 10 laps) thinks of it as recursion as opposed to keeping a mental index and thinking it similarly to a loop (without knowing the terminology of programming of course).
>In any case, lots of languages have gotten map and filter, and people seem to adapt to them just fine.
Those are just abstracted loops though, not recursion.
> Those are just abstracted loops though, not recursion.
Map and filter on a tree is about as recursive as it gets. Loops are the special case of recursion that does work well on linear data structures like linked lists and arrays, yes.
We may think "do this operation to all the things I have" for things that involve the totality of some items as an unknown number (e.g. pack all clothes, take all dishes out of the dishwater).
But we also think with indices, and very intuitively, for anything involving a specific number of counts, like the example I gave (run 10 laps, I've run 1...5...7...8...10, done).
>Map and filter on a tree is about as recursive as it gets.
Well, the original comment was "lots of languages have gotten map and filter, and people seem to adapt to them just fine."
And while this might be true for array like collections, I don't think people have adapted 'just fine' to map and filter on trees -- I very much doubt it's commonly used at all.
Counter-anecdote:
Oh, if people were good with indices, they wouldn't have so much trouble with ordinals vs cardinals vs nominals.
> And while this might be true for array like collections, I don't think people have adapted 'just fine' to map and filter on trees -- I very much doubt it's commonly used at all.
Python's set and dictionary comprehension encapsulate map, filter and cartesian product. But that might strengthen your point: Python goes to great lengths to make these look like loops.
I guess we need to get a large number of ordinary people and do experiments on them.
But, hey, even C++ got lambdas.
And because customers will pay more for software with bugs in it.