In this post I interpret design patterns and principles commonplace in object-oriented programming languages from the perspective of functional programming. Most of these patterns are trivially reduced to elementary functional constructs. The intent is to illustrate advantages and insight resulting from a shift in paradigm. After all, both the object-oriented and the functional paradigm have a shared goal - the solution of some problem. The patterns discussed herein are a testament to this commonality. The difference between the paradigms lies in the underlying abstractions upon which they are established which in turn have significant ramifications for the problem solving tactics they engender. In particular, I argue that the object-oriented inheritance model and the paradigm’s primary utilization as an adorned state encapsulation mechanism are limiting factors in composing higher-level abstractions. Conversely, functional programming, being based on an entirely different model of computation, eschews state, thereby bypassing accompanying hurdles and mandates composion from the core, thereby encouraging resuable abstractions.
The following claims are based on a few years of experience developing applications with the functional paradigm. In particular, contrasts of the paradigms have been distilled through porting code samples for Implementing Domain-Driven Design from a Java/C# implementation to an implementation in F#.
The analysis of the object-oriented paradigm herein is based particularly on its manifestation in languages like Java, C# and related languages. (Variations of object-oriented programming without state have characteristics orthogonal to abstract data types (ADTs) employed in functional programming). Functional programming examples are provided in F#.
This topic has been addressed priorly. Peter Norvig discussed how most of the GoF patterns are invisible or simpler in LISP, calling the patterns “programming language bug reports”. Paul Graham echoes the notion. Similarly, Joe Armstrong describes why OO sucks. Although both LISP and Erlang (designed by Armstrong) are dynamically type-checked, the arguments hold just as well for statically typed languages. In fact, the issue of typing is orthogonal.
Arguably, the power of abstraction in functional programming can be a double edged sword. For example, a monoid is in essence a very simple abstraction - an interface consisting of two members. However, without some context, it can be troublesome to sense its value and utility. Abuse of such abstractions can lead to cryptic code, however this isn’t representative of the functional approach.
Finally, the intent herein is not to undermine the object-oriented paradigm. In many ways, well crafted object-oriented code converges upon a functional style. This is no surprise because as stated earlier, the eventual goals of both paradigms are one and the same. It should also be stated that given the present state of the art, functional programming isn’t practical for all domains.
In this post I survey the SOLID patterns followed by tactical patterns of Domain-Driven Design. These patterns are intimately related. The single-responsibility principle fosters the interface-segregation principle; the open/closed principle fosters the Liskov substitution principle. The dependency inversion principle fosters Domain-Driven Design.
The central grievances with object-oriented programming as presented herein are summarized as follows.
The distinguishing characteristics of functional programming that address these grievances are summarized as follows.