Just like “mostly secure,” “mostly pure” is wishful thinking. The slightest implicit imperative effect erases all the benefits of purity, just as a single bacterium can infect a sterile wound.
This is transparently false. Imperative code that manipulates global state is improved and simplified as you start applying immutability and controlling side effects, even before you have transformed the entire program. A type system that lets you track side effects is indeed very nice, but starting with a hyperbolically false claim only serves to alienate the audience he’s trying to persuade.
Yes, mostly good is good enough to ship and mostly maintainable is a huge improvement over what we usually deal with.
Meijer is able to prove his point at the expense of limiting “work” to “solving problems developers face with concurrency, parallelism (manycore), and, of course, Big Data” and limiting “mostly functional” to “continue the ongoing trend, embrace closures, and try to limit mutation and other side effects.” But this is doubly a straw man.
First, even if we accept Meijer’s provincial* definition of “mostly functional”, it’s absurd to pretend that its only benefit is performance and concurrency improvements. Second, even if we’re only interested in performance and concurrency improvements, a wider variety of “mostly functional” techniques provide an excellent way to achieve performance and concurrency benefits, though not the strawman techniques Meijer considers.
* I know that Meijer himself is not provincial, but still he is applying this provincial C#-ish definition as if he had never heard of ML, Lisp, or Rust. Why?
Mostly functional programming was invented in 1959 with LISP (or arguably with IPL), and for 35 to 45 years suffered from a stigma of carrying an unbearable performance cost — thus Perlis’s quip that Lisp programmers knew “the value of everything, but the cost of nothing.” But a substantial community of lunatics continued to use mostly functional programming languages, and even purely functional programming languages like Haskell; the reason was that these languages allowed them to focus on higher-level concerns and write programs that would have been unmanageable in non-functional languages, and indeed, a large fraction of the advances in systems and language research during this period came from Lisp and other functional languages.
These advantages continue today, and they accrue incrementally.
For example, today my pair and I were working on a Java program in which I had a method that took a GregorianCalendar argument. We needed to modify the date calculations it performed slightly: specifically we wanted it to do the same calculation as before, but starting from a date one month earlier. We adjusted the tests and added this line to the implementation code:
Mysteriously, some of our tests began failing in an apparently impossible way. After some thought, we understood the problem: we were mutating a date object shared with the caller, which was then shared with the next call to the method, which would then do the calculation based on a date two months earlier instead of one. A .clone() call solved the problem.
If we had instead been using JSR-310 Instant or DateTime objects, which are immutable, this class of bugs would be ruled out by construction, along with a large family of concurrency bugs and the performance bugs that appear when trying to avoid them! In fact, most of our codebase uses an immutable Moment class similar to JSR-310 DateTime, but this particular part of the codebase is supposed to be isolated from that dependency.
Quite aside from whether a particular modification to your codebase is prone to bugs such as the above aliasing bug, it is often the case that a mostly-functional design will allow you to make that modification with less effort. By improving the orthogonality between data structures and algorithms, it allows you more flexibility along unexpected axes.
(Unfortunately, I’m out of time right now and don’t have time to come up with a good, immediate example here, but the programming world is full of them!)
The other day I was generating a gigabyte or so of data files from several tens of gigabytes of other data files, using a small Python program I wrote more than a year ago, controlled by a Makefile. Each command executed by the Makefile generates a single output file from a single input file. After a few minutes, I realized that my four-core laptop was mostly idle, because make was only running one command at a time! So I hit ^C, which automatically recovered from the failure by deleting the partially-created data files, and restarted the make command with -j 4, a GNU extension which runs independent build steps in parallel. Fifteen minutes later, my machine had done an hour’s worth of execution work and was finished.
It’s reasonable to consider this process “mostly functional”, because each Python program is being treated as a pure function from input data to output data. This allows it to be transparently and automatically parallelized on an SMP machine with relatively low overhead. And this is true even though the Python programs themselves are written in a shamelessly mutating fashion, because their mutation is limited in scope to their own process memory; they don’t leak out and read and write data in the rest of the system.
This is a point with very broad applicability! Most of today’s really cool opportunities for concurrency and parallelism are rooted in one or another kind of “mostly functional” programming, whether we’re talking about Hadoop MapReduce or Spark, which are kind of parallel cluster extensions of the make model, or GPGPU, where we do most of our execution in shaders that run to completion without access to main memory.
Limiting the term “mostly functional” to the long-overdue addition of little-used functional features to existing imperative languages (OO or no) is failing to see the forest for a tree stump.
This is an excellent point. “Mostly functional” isn’t inherently bad. Machines don’t care whether the source code is “functional” or “object-oriented” or “imperative”. And there’s some exceptionally good imperative code out there. There has to be; our functional languages depend on it for their runtimes and to interact with the outside world.
“Mostly functional” fails as a style guide, when you have unskilled programmers or tight deadlines and people are going to take the path of least resistance. Then you get people bypassing the FP discipline when you need it the most. Insofar as functional discipline and type discipline highlight design flaws, the places where unskilled people (or skilled people under unreasonable time pressure) feel compelled to “let up” are exactly where the discipline appears, in retrospect, to have been most needed. Scala’s hybrid FP/OOP approach is a disaster, for example, because it lets businesses continue with business-driven engineering (crappy coding practices, bad project design, ridiculous deadlines, general disengagement on the engineering front) while granting the power to generate monstrous complexity.
It’s also unclear to me what qualifies as “purely functional”. Do Haskell’s IO and ST monads count as “impure”? Because if that’s the case, then it’s hard to see what “purely functional programming” is usable for.
Finally, I think it’s important to make the distinction between imperative programming and object-oriented programming. And then, to distinguish between Kay’s vision for OOP and the shitfest that is business OOP. I’m a card-carrying FP zealot, and corporate OOP code can die in a fire, but I recognize the beauty of clean imperative code.
Great point about pure processes and the “filesystem monad”.
This article seems more about “mostly OO with functional stuff”, rather than “mostly functional”. I associate the latter with languages like the ML family, where nonlazy functional is the default, with easy access to side effects, rather than languages built on side-effect semantics with easy access to functional concepts.
Hmm. The call to action in the first paragraph demands lazy evaluation, but nothing in the article explains why. As an imperative programmer, I’m much more comfortable with strict evaluation.
On the lighter side, I no longer need to compare monads to burritos. Now they’re pollution factories.
This would have been a better piece if it didn’t assume familiarity with Haskell syntax.