80 comments

[ 2.2 ms ] story [ 135 ms ] thread
It has! Almost all major languages today let you pass functions (and closures with captured bindings) as values.
That's not really what functional programming is about. Functional programming is more about avoiding state and referential transparency. Most programmers do not attempt to do this in mainstream programming languages.
avoiding global/shared state :) we are not avoiding state as per se. State if super important! That's why we care about it so much. But Im just nitpicking, sorry
I meant "hidden" state because functional programs still reason about state, but more explicitly. In a functional style you can't just call a function that relies on hidden state somewhere, you have to pass that state in!

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.

Well, using a state monad such as Haskell's IO monad is usually considered functional style, but the state is hidden. Moreover, any body of code in which state is passed around in explicit arguments can be transformed into code in continuation-passing style (or monadic style) in which the state is hidden -- and it's hard to argue that the code in continuation-passing style is not functional code.

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.

IO monad doesn't hide state, it says right in the type that it's IO. Neither do other monads that "hide" state according to you, because they actually specify that they use state right in the type signature.

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!

It's what functional programming was about for the first twenty years.

That and everything being an expression that returns a value.

20? More like 40. Until 2005 or so few considered this "pure" style essential to FP.
While state/referential transparency/immutability are important (and great features), they're more associated with pure FP. Purity is not a necessary precondition for a language to be classed as "functional". I'd argue that the defining feature of FP is the treatment as functions as first class values. Everything philosophically speaking, flows from this fact.
Even Python has immutable strings and numbers.

Mainstream languages are slowly moving towards more and more functional concepts. The progress towards referential transparency is slow, but ongoing.

Isn't performance a problem? Technically you can memoize to eliminate constant recomputing of state, but that itself is overhead. Sometimes I try to use a functional style in common languages and end up backing out when it comes time to optimize.
It depends on what kind of performance problems you have.

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.)

React's new stateless components are very functional in that regard, and are being adopted rapidly
Not "taken over" but in many ways functional programming techniques have gotten big in front-end land, not only with JavaScript but also "real" functional languages like Elm or ClojureScript.
those are both javascript? thought you were going to say haskell or clojure...
Both compile to JavaScript, yes, but are their own languages used to write front-end applications. That's what I meant.
Good point, languages like JavaScript support both imperative AND declarative/functional styles. A language that lets you do both covers more use cases than a language that only does one. With a multi-paradigm language like JS, companies can choose what approach they want to use (and to what extent) and enforce it via a programming styleguide. With JavaScript, you could even run a linter over your code to strictly enforce your own FP rules.

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.

This is where I think clojurescript (particularly with React) really shines. Pure functional is the default, and you have to actually do work to make things stateful. The main pitfall of almost-functional languages/frameworks is that it's all too easy to fall back on stateful programming to get things done quick-and-dirty.

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.

I'll toss it out there that 100% pure FP is pretty much the epitome of keeping a stack of state somewhere... But I wouldn't claim it's too similar or intuitive to someone more used to standard state management.
What do you mean by a stack of state?
I'm copying the op who I actually think meant to say "keep track of state", but "stack of state" feels like having a bunch of "pieces of state" all tidily stacked up in the corner so despite the potential confusion with the technical term I kept it.
> Good point, languages like JavaScript support both imperative AND declarative/functional styles. A language that lets you do both covers more use cases than a language that only does one.

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.

If you use the ST monad instead of State, you get actual mutable variables. STArray lets you write typical imperative algorithms that work with mutable arrays, like sieve of Eratosthenes or dynamic programming.

Imperative sieve of Eratosthenes:

https://gist.github.com/rik0/1171446#file-erat1-hs

Yes. Though sieve of Eratosthenes really cries out for a sort-of-lazy infinite array.
Because it doesn't have enough advantages to justify the added complexity.
extremely testable, easier to reason about, automatically dry, parallelizable, automatically reusable, memoizable, composable, can be analyzed with some theoretical systems, and most important- you get stuff that works better (in my experience)
That's great, but how do I write a functional version of https://github.com/voltagex/junkcode/blob/master/CSharp/spot...?

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.

Either that or take a look at Elixir. All the 9 9's reliability and distributability of Erlang with a more Ruby-like syntax.
Dunno why you were downvoted. Elixir is on my list of things to look at.
I'm sure it was a misclick. ;^)
Sorting and filtration are super bread-and-butter FP. The "hard" bits are the IO to get the results and to print them out. Solve the first with a nice HTTP client lib like Wreq, bring in the IO language to do it, finish it off using putStrLn to print. It's neither the same mechanism as you're used to not difficult.
LINQ in C# and lambda in Python are as close as I get to FP at the moment. I may have another look at F# and someone else in this thread has reminded me to look at Elixir.
> The last time I looked at Haskell, it needed ~700MB of dependencies before I could even get started.

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.

I suspect the 700MB dependency is Haskell Platform. Which you don't need and is not considered a good way to install Haskell. Just GHC and Cabal (package manager, sort of ) are nowhere near there. Or even better, use Stack. See how large the binaries are.
There are ways to write things that are similar with Haskell. Just like there are design patterns for functional techniques in imperative languages, there are design patterns for imperative techniques in functional language....

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)...

Thanks for this. The syntax is definitely taking a bit to get my head around, but it looks very tidy.
If I get some extra time I'd love to post a build able Haskell clone of this.
Functional programming also emphasizes the use of recursion, which nearly always makes an algorithm much harder to understand.
No, you may have to think about it a little more, especially when first getting acquainted with recursion, but the recursive solution is likely to be less complex and more robust. It's the repetition of a simpler idea, and so there are fewer ways to screw-up the implementation.
I disagree. Recursion makes a stack trace that should be 10 functions deep into one that's 1000 functions. It's difficult to debug and follow the code around, or even to understand the code if someone else wrote it.

Also recursive functions tend to spiral out of control and have ridiculous exponential run times.

> I disagree. Recursion makes a stack trace that should be 10 functions deep into one that's 1000 functions.

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.

PS Another way to implement function calls (and thus recursion) is transformation into continuation passing style.
You sound like someone who went down the Haskell rat hole.

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.

Correct me if I'm wrong, but aren't both of these concerns largely covered by any language that has tail call optimization (basically any functional language)?
Some of them.

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.

True to a degree. Languages that promote the use of recursion often implement tail call optimization so if the process is an iterative recursive process (all state is captured in args on recursive call) then the perf issues you mentioned don't apply. The latest version of JavaScript will include this optimization.

I also disagree wrt it being more difficult to debug, understand, etc. Your mileage may vary here.

Recursion really only works in languages that support it well. In some languages recursion is simply an unnatural act.
Why? First, loops are only a special case of recursion, and trying to shoehorn everything into that special is actually hard to read.

Second, functional programming usually emphasises combinators like map, filter, reduce/foldr. Those are easier to read than either naked recursion or loops.

>Why? First, loops are only a special case of recursion

In theory. In the actual world, loops are how we're used to think about doing N things.

If `we' is imperatively trained programmers, then Yes.

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.

>If `we' is imperatively trained programmers, then Yes.

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.

I'd assume laymen think in terms of combinators, ie like map (do this operation to all the things I have), or filter (through out all the widgets that fail the test). Thinking in terms of indices is pretty painful.

> 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.

>I'd assume laymen think in terms of combinators, ie like map (do this operation to all the things I have), or filter (through out all the widgets that fail the test). Thinking in terms of indices is pretty painful.

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.

> 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).

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.

Recursion is great! I find recursive algorithms much easier to understand
Explicit use of recursion in functional programming is widely considered to be low level and bad form, like writing all your loops with gotos in a procedural/OOP language (as was the primary argument of the Bananas, Lenses, Envelopes, and Barbed Wire paper), it's just commonly used in intro material in a "this is how you would do this by hand" fashion. For things that fall outside of that, you always have the option of monadic loops if it makes the code clearer.
It's rather less complexity. But for switching all your legacy ecosystem (and accumulated knowledge!), FP doesn't have enough advantage for that at the moment.

But, hey, even C++ got lambdas.

I've noticed that programmers tend to become more interested in functional programming over the duration of their careers. I think this is because, as a paradigm, FP solves problems that only experienced programmers realize they have. I'm not sure what the age distribution of working programmers is, but I bet the median is in the low 30's at this point, which is when you start being interested in FP in a real way.

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.

Very well said on both accounts.
> as a paradigm, FP solves problems that only experienced programmers realize they have

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?

beautifully put. the first part is a specific case of a more general pattern i see in myself and others, where an older more experienced person does something in a way i think is silly, then i see the wisdom in it later. i think tech is, in general, ridiculously dismissive of experience. not too controversial an opinion i know, but i think it bears repeating.
Because most people are not smart.

And because customers will pay more for software with bugs in it.

Careful, you may cut yourself on all that edge.
At least the software will be ready on time.
>Stateless programs; No side effects

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.

There are not 10,000 other WhatsApps, FWIW. By any useful measure. User base, concurrent active sessions, both of those relative to size of dev team and/or infrastructure footprint, acquisition price, user growth rate, etc.

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.

>There are not 10,000 other WhatsApps, FWIW

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.

> >Stateless programs; No side effects

> Which is not all it's touted up to be.

It's not even true!

Possibly because the way we learn math, and the math that most of us learn, is taught procedurally. It's the mindset that many of us grow up with.

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.

Because programming is primarily a business and it's cheaper to hire dumb programmers for whom functional programming is too difficult.
Between Excel and SQL, I'd say collection-oriented programming has pretty much taken over the world.
One could make a case for a spreadsheet being a limited kind of pure functional programming language.
Not pure, because you have state at the top level.
Sure, but it isn't being modified by the expressions in the cells.
True, and that's a good way of putting it.