Functional Programming Is Not Popular Because It Is Weird
probablydance.com
probablydance.com
The "lambda the ultimate" papers are the classic example of using functions to implement a variety of language features, including jumps. Hence the author's claim that functional programming is an "inside-out" approach is more of a question of style than of paradigm.
For example, even simple function composition can approach the clarity of imperative code for following a step-by-step process. The result tends to look like UNIX pipes, although traditionally they're read "right to left" (but you can trivially define your own composition function which goes the other way).
Here's an example of the author's task, written in a compositional style rather than an "inside out" style:
heatedOven = preheat 175 oven
preparedPans = grease . flour $ pans
flourMixture = whisk . add flour . add bakingSoda . add salt $ smallBowl
creamedMixture = until (all [light, fluffy]) . cream . add butter . add whiteSugar . add brownSugar $ largeBowl
bananaMixture = mix . add bananas . repeat eggCount (beat . add egg) $ creamedMixture
batter = stir . add (chop walnuts) . repeat (size / spoonful) (add (spoonful flourMixture) . add (spoonful buttermilk)) $ bananaMixture
baked = take . wait 30 . add (add batter preparedPans) $ heatedOven
cake = take . wait 10 . add baked $ dampen teaTowelbut
bakery monads are what is needed if you want to bake hundreds of thousands of cakes of hundreds of different variations (types) in a manageable manner.
FP starts to make (more and obvious) sense there not in the small household kitchen. But even then that is not to say that FP doesn't make sense in a small household kitchen. Only, it is very difficult to make sense of that sense at the smaller scales.
The correct image here is all the stuff -- oven, pans, butter, flour, salt, etc -- entering a funnel on the left and producing a baked cake on the right. The funnel itself, if you were to peer inside it, would be composed of multiple sub-funnels: pans with batter + oven as the last step, empty pans and bowls of batter as the step before that, and so on.
It's natural, not weird. The metaphor is used all the time on cooking shows, where they show you bowls of all the ingredients, and the final product next to them.
No. Shit. Sherlock.
I’d say the same thing about human languages. Can you imagine me criticizing Japanese because when I try to translate the English in my head to Japanese on a word-for-word basis, it’s a lot of hard work and the result is terrible?
The point of learning a different language is not to learn how to translate what is in your head into whatever the language demands, it is to change what is in your head. Alan Perlis put it best:
> A language that doesn't affect the way you think about programming, is not
> worth knowing.
What I would say, is that if you know language X and want to compare it to language Y, you need to keep working at Y until it affects the way you think about programming. If it doesn’t, you can then say that it failed to improve you as a programmer. If it does, you can then give a thorough review of how your new perspective has improved you, and whether it is worth the effort.I’m not saying FP is better or worse, but I am saying that “It’s terrible for writing the imperative programs in my head” is not a good argument.
The reason he says it's not popular is because the vast majority of people think imperatively when dealing with a problem that involves state and time. They might think functionally when trying to express an immutable law or when categorising the qualities of a horse or whatever. But when doing something that involves changing the state of the world in time and space (which is a significant part of many programs) they think imperatively. And even C will support enough of the functional stuff to do the rest (see Numerical Recipes, for example)
Those can be written in any style we like. For example, in languages like JavaScript and Ruby, if we wish to compose classes out of smaller components, we can imperatively modify classes, or we can write functions that take existing classes and return new classes consisting of the originals composed with whatever additions we desire.
What I am trying to say is that while imperative may feel more natural for problems involving state and time, we often perceive that problems require state and time even when they don’t.
---
I can fully accept the argument that people don’t like FP because they perceive that their problems need imperative solutions. But that is not the exact same argument as “people don’t like FP because their problems involve space and time."
I like "solving puzzles" but I don't like solving them under pressure. I also don't like deciphering someone else's puzzle when I am in a hurry and when I'm unlikely to never need this piece of code again. So even for people who like puzzles it's often the case that they just need to get something working fast that's reasonably correct.
The fact that life is short and that the world changes so rapidly is what makes worse better.
Meanwhile, FP does introduce some of its own problems, and I think many of my fellow FP fans are too quick to downplay them. Performance is one. It might be solvable by compilers, but for those of us who are not likely to spend much time hacking on the compiler for our language of choice that's a small consolation.
The end result being, it's still the case that my adventures in functional programming (which are generally done using a multiparadigm language like F# or Scala) will go something like this more often than I care to admit:
1. Implement imperative solution.
2. Feel guilty, go back and replace it with functional one.
3. Find performance problem. Dig out optimizer.
4. Optimizer points to code from step 2.
5. Revert back to version from step 1.Anecdotally, I have found a couple of times that I needed to manually implement a beautifully simple map/filter/fold call sequence in Rust because it was in a hot loop and just wasn't performing as well as a dynamic programming version that I wrote in the course of benchmarking. I've also found that plenty of times the functional style beat my hand-rolled code, which is an absolutely fantastic result, but I don't think that the compiler is yet sufficiently intelligent to replace all of my hand-rolled loops :).
I've felt this way [1] about Python, well, more than once. I'm feeling the same way by learning Haskell.
Except that with Python I learn the good parts fast, and then it's back to normal life. With Haskell things keep getting better and better... It's in part because Haskell is harder, but the improvements are also much larger.
Here's the one billion dollar question. Which is harder?
1) Correct code
2) Fast code
3) Available code
4) Familiar code
The history of the software industry has basically been 40 years of customers choosing code that exists, solves their problem right now, and doesn't make them think too hard about how to use it. They choose this over correct code (hence, Microsoft) and over fast code (hence, Python/Ruby/Javascript/PHP).
FP actually helps you write both fast and correct code. The problem is that it requires a good deal more planning up-front and more effort to change things when requirements change. This has usually been a non-starter with the majority of customers: they want software that does what they need it to do, right now, and will continue to do what they need it to if they change their mind. If it screws up occasionally or makes people wait, that's okay, it's still faster & more accurate than a human is.
You can't just look at what fans of functional programming say. That's a self-selected group, so you're dealing with an observation that may be biased for any number of reasons.
The closest I've come to seeing an objective comparison was when I was TAing intro CS classes in undergrad, so I had the opportunity to see the same people have to use both. In that case, it seemed to me like folks had difficulty writing correct code regardless of paradigm. The difference was, with the imperative language you'd see a lot of mostly-working code with a few bugs. With the functional one you'd see a lot of failing to even get off the ground.
If we assume, for the sake of argument, that that's a reasonable characterization of what the typical person experiences when they try FP, is it really any surprise that it's not more popular? Nobody wants to work in a language that makes them feel frustrated and/or stupid. And you're not sweetening the deal at all when you insult them by implying that their code is inherently buggy because of the language they use.
I'm a little curious as to why you find FP performance problematic. I guess you must be trying to solve some pretty CPU-intensive problems?
Anecdotally, I tend to write Haskell "top-down", e.g. writing QuickCheck tests for top-level functions, writing dummy definitions with `undefined`, and fleshing things out as and when the tests require it.
You can't pick up C in a couple days and write programs if you don't understand pointers, const's, etc.
>whereas many C/C++ coders naturally decompose problems in a top-down manner.
That's kind of weird, considering this quote from Ken Thompson, when asked how he thinks of such good abstractions:"It is the way I think. I am a very bottom-up thinker. If you give me the right kind of Tinker Toys, I can imagine the building. I can sit there and see primitives and recognize their power to build structures a half mile high, if only I had just one more to make it functionally complete."
EDIT: To clarify. I am agreeing with the original author. If Functional Programming is as great as it is supposed to be it would have been eating everyone's lunch by now. It has been around forever, yet it remains quite niche. This indicates strongly that there must be underlying reasons it is not more popular.
I have to say that I rather like working backwards - yes, a finished cake really is cooled after baking after batter that has been mixed. If you're putting together an engine in your garage you work the same way - nobody rebuilds their carburetor while it's mounted on the intake manifold (okay, not many people even have carburetors anymore). You build "assemblies" and create the larger whole from sub-assemblies. The same applies to virtually all manufacturing ... I spent a lot of time designing electronic systems that all started by populating circuit boards.
In one of my classes freshman year, one of my favourite professors (Kathi Fisler <3) gave us an assignment where we made a silly game with an airplane that flies around and drops things. And I remember wondering how to do that without mutable state for the location of the plane, etc. Then I just remember seeing in the instructions for the assignment something like:
> write a function that takes a world and returns a new, updated world with the plane at its next location
and I just remember sitting there with my mind completely blown. Looking back at it, it doesn't seem as profound as I thought it was at the time, but maybe that's just because the novelty has worn off for me and a few years in industry have made me cynical, who knows :-)
Anyway, I think just learning to look at a problem differently I think gives you valuable insight into it and into your mind. Doesn't matter if you end up using it or not.
> I spend too much time trying to figure out how to say things as opposed to figuring out what to say.
Ha! Again, I think this is just a different approach. When I tried out haskell, it was intriguing that the modus operandi was: "think a lot about a problem, then write down a little code". I like that this is the opposite of my instinct to jump in with a solution, then kinda heuristically change it to get to code that works, and ultimately I think lead me to think a bit more and end up with clearer problem definitions and solutions.
http://web.cs.wpi.edu/~cs1102/a08/
and OMG! I found the assignment!
http://web.cs.wpi.edu/~cs1102/a08/Assignments/Hwk2/index.htm...
> A function update-world that consumes a world and produces a world reflecting what happens when no key is pressed
OK, not phrased quite how I remember it but I guess that's where the epiphany happened :)
I took another class with her that was programming languages, and we used Programming Languages: Application and Interpretation (https://cs.brown.edu/courses/cs173/2012/book/) and the course website (http://web.cs.wpi.edu/~cs4536/a14/)
This class also had some great epiphanies for me, but again, I think the instruction being top notch helped keep my attention going. This is more advanced stuff conceptually.
Because FP relies on an insane amount of theory.
Often FP aficionados are incapable of explaining functional programming concepts to others without using FP idioms or jargon. I still don't have the foggiest idea of what a monad or a functor is, or why the hell would I want to use currying or partial application, when my imperative code just works (that's not entirely true, and javascript has done a great job at popularizing some concepts, like closures, partial applications or first class functions).
Now compare with OOP : polymorphism , inheritance and encapsulation are easy to explain : Everybody can understand that a machine can be turned on by pressing a button, no matter what machine it is(phone,computer,screen) : polymorphism. It's easy to explain encapsulation : you don't need to how the inner working of a microwave in order to use it. Same with inheritance.
I agree that one should learn at least an FP language so one is forced to deal with a specific problem a certain way, that's good exercice. But I shouldn't have to understand category theory in order to learn Haskell or Clojure.
I think you're conflating Haskell with FP, which is definitely not the case. No one needs to understand category theory to write Clojure or Lisp.
The thing is, all the lucrative software engineering positions created in the late 90s required knowledge of OOP. Which meant that all the wannabe software engineers put in the time and effort to learn it, because that was how they could get a high-paying job. And so now you have a critical mass of people for whom OOP is second-nature, who can explain things to junior developers when they ask questions.
Follow the money. FP isn't popular because outside of certain niche domains (eg. high-assurance software), it doesn't provide a competitive advantage large enough to build billion-dollar industries off of. If knowledge of FP suddenly started becoming necessary to get a six-figure software engineering job, you'd see many more people putting in the effort to learn about FP concepts. (This may already be happening with React in web development, with Hadoop/Spark/MapReduce in big-data, and with Swift in iOS development, but it's a new trend that'll take years to mature.)
The fact is, for an industry that talks so much about lifelong learning and innovation, software developers are very conservative.
You can learn what is a monad or an functor by reading/writing code with them. Pure Math is incredibly hard to learn without context.
It's a bad analogy though.
"It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration."
1. Take an oven and apply 175 C setting on it, producing a preheated oven. Set this aside.
2. Take two 8 inch round pans and some grease and flour, produce two 8 inch round pans with grease and flour applied on them.
3. Take a small bowl, some flour, baking soda, salt, salt and a whisk (a first-class function), return a small bowl with flour, baking soda and salt whisked in it.
and so on.
When you think of functional programs as series of operations chained together, it becomes really natural. In most recipes you don't need to loop back to an existing state.a
If you want to argue with this, just deny that cake recipes are a good metaphor for programming, since they are essentially imperative and that is begging the question. But you aren't going to convince people that weirdly abstracted cake recipes are easier to write or follow.
(You can tell that monads are hard to work with because there are so many articles telling you that they're easy)
The common ground is functional-ish and mostly-stateless programming ("const" etc). C# is actually becoming very good at this, and you can always cross into F# if you want.
No.
> The common ground is functional-ish and mostly-stateless programming ("const" etc). C# is actually becoming very good at this, and you can always cross into F# if you want.
Newish C# is just functional programming dressed up as Java, and goes far beyond const. C.f. functional-ish C# programs to C programs that use const. Striking difference.
F# is just .NET's version of a straight up a very traditional and canonical functional language. If F# isn't functional then nothing except maybe untyped lambda calculus is functional.
The article's title is correct only in the tautological sense that unpopular things are weird, even in the paradoxical case where those unpopular things are actually fully embraced while remaining unpopular.
> (You can tell that monads are hard to work with because
> there are so many articles telling you that they're easy)
lol yeap, you made me curious so I went to search for those articles, and sure enough, there are plenty. My favorite quote was: "Monads are easy, you just need to put in the work."My second-favorite quote was: "if someone is really convinced that monads are easy, they are going to be in for a demoralizing shock"
"A ‘newbie’, in Haskell, is someone who hasn’t yet implemented a compiler. They’ve only written a monad tutorial"
You can use something like Clojure without touching monads, just use functional data structures to represent values instead of using objects, and the using mutable state is simple just not the default and you rarely need to go there.
In fact if you're using something like React and something like Redux you're going in that direction from the other side - in Clojure it's basically the natural way to solve the problem (and frankly all of their concepts map much better to CLJS than JS)
I feel like people pushing Haskell and all of the weirdness that comes from lazy evaluation did more harm to FP perception in the general programmer community than good.
You've hit the nail on the head. Haskell has the combination of being very extreme and popular enough that it is damaging the perception of FP as a whole. I really wish Haskell didn't exist.
For anyone skeptical of FP who might be reading this: start with a dynamic FP language or a typed FP language that is strict rather than lazy. Examples of the former are Erlang/Elixir or Scheme. Examples of the latter are OCaml, Elm, F# or good old Standard ML.
* IEnumerable<T>
* IObservable<T>
* IQueryable<T>
* Lazy<T>
* Task<T>
You'll rarely hear the term monad in the MSDN documentation though. Maybe it's opaque terminology that scares people away from monads rather than monads themselves.
type State = Locked | Unlocked
type alias Model = State
lock : Model -> Model
lock model = Locked
unlock : Model -> Model
unlock model = Unlocked
open : Model -> Bool
open model =
if model = Locked then True else False
and here it is in erlang: lock(_Lock) -> locked.
unlock(_Lock) -> unlocked.
open(locked) -> false;
open(unlocked) -> true.
i think working with state machines in imperative languages is messy, mistake prone and hard to reason about. functional languages are just better for managing state > The humble state machine is a more important part of programming
> than people like to admit
Here is where I feel a lot of OOP applications are “doing statefullness wrong.” Everyone gloms onto the popular GoF patterns, but explicit state machines are not nearly popular enough.A colleague once remarked that in business programming, every model should be a state machine until you can demonstrate you can do without. For example, many Ruby on Rails models have a concept of validation. Most such models can be greatly improved by treating “invalid” as an explicit state with its own methods and behaviours distinct from the methods and behaviours of a valid model (which may encompass other states as well).
I find that if you’re going to have state, go all the way, reify it, make it something you can name and manipulate. Implicit state is the GOTO-ridden spaghetti-code of imperative programming style.
Honestly that makes sense to me, and I don't blame programmers who are happy with their jobs/lives/workflow/whatever from switching. But when the mainstream idolizes "disruption" and "innovation" above all else, y'all start looking mighty hypocritical. Please, for the sake of us functional programmers, choose one of your revolutionary aesthetic or your shitty languages, but not both.
https://github.com/melling/ComputerLanguages/blob/master/oca...
I've been collecting my notes for a while on Haskell and OCaml. After reading this article, it seemed like OCaml was a good FP choice to start with:
http://roscidus.com/blog/blog/2014/06/06/python-to-ocaml-ret...
In fact I think the cooking recipe analogy does way more harm than good. And let's be honest, there's nothing weirder than X = X + 1, being the number one reason for why many people can't understand programming.
> Both humans and machines are really good at implicit state attached to time.
That may be true for machines; they always know where all their state is. But we humans are terrible at managing state, especially implicit state, especially when we have to reason about how it changes as time passes. We can't keep all that state in our head, and as soon as a system gets reasonably complex even simple values mutating get hard to juggle in our puny minds. I would venture to guess, unscientific as it may be, that a majority of bugs in software have to do with humans not being able to reason effectively about state.
Pure functions taking immutable data in and spitting out new values eliminate many of the challenges with managing state. It's vastly simpler for us humans to understand what's going on with our state when we deal with chaining together a bunch of functions manipulating data immutably, and push managing mutable state out to the very specific points at the edge of the system. It's not a silver bullet--but it helps. And it's not weird at all, once you're used to it; quite the opposite.
> Writing functional code is often backwards and can feel more like solving puzzles than like explaining a process to the computer.
> I'm writing this blog post because I ran into a related problem recently. C++ templates are accidentally a functional language.
Functional programming advocates can't even agree on a definition of what functional programming is, and I'm even less sure what the author intends. What they have chosen to highlight is not what I recognize as functional programming; while I think it's good the author wants to talk about immutability as one facet of FP, it seems pretty bizarre that they claim that functional program often dictates you write your code backwards, or that they choose an already implemented solution and their attempt to refactor it functionally using C++ templates as a place to critique functional programming in the real world.
More generally, the big problem with these articles is that they fundamentally focus on the wrong things. Whether someone has issued a blistering indictment of OOP and all its failures as a methodology or we get a piece like this, we end up arguing over nonsense because we don't have a standard definition of OOP or FP that everyone can agree on, and it's the wrong level of abstraction to be arguing over anyways.
What we should be talking about are specific language features and what they bring to the table, including how they interact with other language features, how they are implemented in various environments, etc. What does immutable data do for us? How can pure functions help us reason about our code better? What kinds of type systems are out there, and what positives or negatives do they have in different contexts? Does encapsulation bring anything to the table or is it a net negative? Etc.
I'd encourage anyone considering writing yet another glib takedown of OOP or FP to take a step back before they put text to screen and consider instead discussing specific features that have widely agreed upon definitions and how those programming language features lead to certain outcomes, without making a dramatic overarching statement about the weirdness or badness of OOP or FP. Pieces like this only further reinforce the superficial pop culture that currently dominates our industry.