Writing code is the primary activity of software development. Writing the right code, code that delivers customer value, is the hard part. But right or wrong, the main thing that developers do is work in an editor, transforming inputs to outputs.
For more than a decade, most mainstream developers have done their work guided by the concepts of object orientation, which says that programs should be structured in a way that mirrors real-world objects. The fundamental concept of an “object” is a software module that combines the functions that operate on data and the data itself an object instance for a customer. For instance, it could store the data of a particular person’s name, account number and so forth.
This is so ingrained that it may seem natural to those who have known no other way, but 25 years ago, it seemed an academic theory with dubious benefits. As an alternative to the behavior-oriented “structural programming” model and the data-oriented world of “fourth-generation languages,” object orientation sometimes seemed like so much mumbo-jumbo, and other times it seemed like it was belaboring well-known principles.
But, over time, object orientation has become the norm. Its dominance is, in my opinion, as much a matter of happy coincidences as any inherent virtue; I utterly believe in the virtues of object orientation, but its uptake in the industry was largely driven by promises of code reuse that were never achieved, and an accidental association with GUI programming, which caused a wholesale reset of development techniques.
Today, we are in a similar situation with functional programming. It will not replace object-oriented programming in the way that OOP replaced structural programming; the concepts of “class” and “object” are clearly attractive for structuring software modules. Rather, functional programming techniques will become ever more common, and programming languages will evolve to have more and more functional features.
It’s always easy to predict what has already happened; in truth, functional programming approaches have quietly become commonplace. I say “functional programming approaches” because what I’m talking about is not at all the “pure functional” theory-driven model. Rather, from a strictly pragmatic point of view, if you compare code written at the turn of the millennium with code written today, you’ll see today’s code has less internal state and more context-carrying arguments, and you’ll see yesterday’s code uses object-structural techniques where today’s developers would use first-class functions (that is, where an OOP developer might define a single-method interface to capture the idea of a sorting strategy, the functional programmer would simply pass around a particular function as the implementation).
Such functions are by far the easiest to unit-test. Functions that take several different parameters, or are complex monolithic objects, or that squirrel away the results of their calculations, simply require more legwork to test. Programmers, admirably protective of their own time, have slowly been adopting functional forms without necessarily knowing or caring that these forms are “more functionally oriented.”
The guardians of the functional programming gates will bristle at these statements. Functional programming, strict type systems, and category theory are the bread, butter and champagne of the academic computer-science world. It’s not the ease of testing that’s important; it’s the ability to curry recursive functions without named values. If you say so, Professor.
The fans of a programming language or style inevitably say that their way is “faster to write, more elegant, and more readable.” Functional programmers are no different, but their justification is the close association of functional programming with math, and a tendency in the functional world to value dense compositions over white space.
A more practical boast is the emphasis on “comprehensions,” the loop replacements of the functional world, which are undoubtedly briefer and generally easier to follow than imperative loops. The success of Hadoop and others shows that reasoning about “Map” and “Reduce” (a.k.a. “Fold”), even when distributed across multiple machines and cores, is more attractive than imperative schemes to achieve the same thing.
Concurrency is to functional programming what the GUI was to OOP. The Manycore Era is upon us, and developers are beginning to wake up to the fact that shared common state is even harder to deal with than manually managed memory. Unfortunately, the structural model for OOP is shared common state! Objects are conceived of as separate individuals in memory, but in the mainstream languages, they are not isolated from different threads, and they are free to create, block, and stop their own threads. This way lies madness.
Functional programming says that the output of a function should be fully determined by its inputs (not OOP’s “inputs plus the current object’s state”). This is a fundamental advantage in reasoning about how functions can be distributed and parallelized. Writers (including myself) have perhaps overemphasized this theoretical advantage to create the unfortunately common belief that functional programming “solves” concurrency. It doesn’t, at least not outside of the airiest pure-functional realms.
“OOP is the natural model for programming GUIs” was an overstatement that turned into a self-fulfilling prophecy, and “Functional is the natural model for programming concurrency” seems to have similarly taken root. Just as GUIs forced the entire industry to retrain itself, so too will manycore. Just as the purity of Smalltalk led to the pragmatic adoption of C++ and Java, the purity of Haskell is leading to the uptake of more pragmatic languages such as Scala on the JVM and F# on the CLR (and functional programming has been a dominant theme in the evolution of C# over the last half-decade).
It’s time to dust off a phrase we all grew sick of 20 years ago: It’s time for a paradigm shift.
Larry O’Brien is a technology consultant, analyst and writer. Read his blog at www.knowing.net.