Recently, upon the recommendations of a few people, I picked up a copy of the book “Beautiful Architecture: Leading Thinkers Reveal the Hidden Beauty in Software”. This book is a great read and includes essays from some of the top minds in software today. Some of the topics covered are:
- How Facebook's architecture is the basis for a data-centric application ecosystem
- The effect of Xen's well-designed architecture on the way operating systems evolve
- How community processes within the KDE project help software architectures evolve from rough sketches to beautiful systems
- How feature creep has helped GNU Emacs gain unanticipated functionality
- The magic behind the Jikes RVM self-optimizable, self-hosting runtime
- The design choices and building blocks that made Tandem the choice platform in high-availability environments for over two decades
- The differences and similarities between object-oriented and functional architectural views
- How architectures can affect the software's evolution and the developers' engagement
But one essay in particular caught my attention was Chapter 13 Software Architecture: Object-Oriented Versus Functional by Bertrand Meyer. In this writing, he steps through an example application of financial contracts, as presented in both a presentation called “Composing contracts: An adventure in financial engineering” by Simon Peyton-Jones, Jean-Marc Eber and Julian Seward, along with an associated presentation by Jean-Marc Eber called “Compositional Description, Valuation, and Management of Financial Contracts: The MLFi Language”.
Bertrand Meyer’s overall conclusion is that given the above functional solution, an object-oriented design, especially supporting such features as closures is better than the functional approach as it can provide higher-level abstractions more supportive of extension and reuse.
Laying Out The Case
Dr. Meyer defines what architecture “beauty” is from his 1997 hallmark book, “Object-Oriented Software Construction, Second Edition” as three objective criteria:
Does the architecture help establish the correctness and robustness of the software?
How easy is it to accommodate change?
Is the solution general, or better yet, can we turn it into a component to be plugged in directly, off-the-shelf, into a new application?
Using the above criteria and the functional examples cited, there are a few issues as noted with his essay such as the lack of data points, lack of detail, and a specific focus on modularity, whereas the functional programming examples had a focus on other criteria such as elegance of a declarative approach. The biggest drawback to this essay is that most, if not all Object Oriented examples revolve around the use of Eiffel, which is a fringe language and not in the mainstream thought in the Object Oriented world. To focus on this versus a mainstream functional language such as Haskell has some serious drawbacks that I don’t think can be reconciled in this essay.
Now, I’m not going to go through the example as I linked them above for you to dissect at your leisure. Instead, we’re going to focus on the single issue of modularity between the two approaches.
To speak to the reuse and extendibility issue, the functional example is heavy in the use of combinators, which are functions that take functions as arguments and return new functions. The idea is that the financial world has a lot of jargon and instead of specifying a large set of contracts ahead of time, we could compose them using a strict set of combinators. This way, we have the ability to define new unforeseen financial contracts with relative ease in a declarative manner. The paper is quite compelling for how to build composable Domain Specific Languages around financial modeling, for which functional programming is widely used.
To the author the idea of defining combinators does not scale to large applications and must be divided into modules. In turn, the extendibility problem arises on how you modularize said code while affecting the fewest modules possible. Another issue is that with a one module approach, this does not help reusability as you wish to have only a subset of those operations imported. Careful modularization can get around these issues and I do not necessarily see this as a stumbling block. Instead with a good strategy on modularization and qualifying imports can solve a lot of these issues. Also, the use of type classes can add extendibility to your architecture by defining a set of operations in which other types can
Another issue raised by the author is state management. The notion of stateless programming is essential to the pure functional programmer. State and unmanaged side effects are indeed the bane of concurrency. Instead, Haskell has the notion of monads which allow for the modeling of not only state, but exception management, as well as input/output operations.
Dr. Meyer doesn’t have a concern about the whether these concepts are hard to learn, as they shouldn’t be, but instead, if they are worth the effort at all. Instead, through the use of Command Query Separation, where queries return results but do not alter the world, whereas commands alter the state of the world and do not return a value. I believe, to rely on this design pattern instead of enforcement through the type system is his undoing. Most imperative/object-oriented programmers do not follow this rule and thus should only confine his comments to Eiffel alone. I believe once again, his focus on Eiffel is undercutting his credibility that well known OOP principles can subsume a Functional Programming example.
This analysis could go on and on, but must this be an either/or battle?
The False Battle
With the exclusion of Java, most modern languages have been evolving to include such concepts as closures, which come from the functional world. Even the Eiffel language that has been written by the author, has incorporated “agents” which are his term for closures. And then we have multi-paradigm languages such as F# and Scala which take more of a functional programming approach with the full support of imperative and object oriented features.
Although at this point in history, functional programming languages are less popular in mainstream development, it’s hard to now avoid their influence. With the increased attention to concurrency and parallelism, the ideas from functional programming such as lack of state and immutability are gaining momentum as we realize the peril of shared state. Instead of this battle, where can object oriented technologies learn from functional programming and vice versa?
How Functional Programming Influences
Let’s try another tactic with this and talk about where functional programming techniques have their biggest influence. Brian McNamara, of the F# team, laid out the argument well in his post “How does functional programming affect the structure of your code” in which he describes its effects in three distinct areas:
- In the Large
Covers overall architecture and high level design. This is where the modularization of our code has its biggest influences. To instill laziness as a default architecture, compose message passing systems and high performance computing concerns is where functional programming can have its greatest impact.
- In the Medium
The medium covers more lower level API decisions that we make. When combined with a good modularization strategy, can yield great results. Many times as well the object oriented design patterns such as the command, builder, strategy and visitor patterns can better be expressed through functions.
Also, the use of discriminated unions, records and collection structures can create concise and rich domain models using pure functional structures.
- In the Small
By programming in the small, we mean at the actual function level. With an immutable by default posture, we are better able to express our values without type annotations and the compiler is better able to make decisions based upon that. Functional composition is also very much in effect at this level as we build more powerful functions from the small pieces to create rich solutions.
By understanding these concepts help us better understand where functional programming has its greatest influence and where we can use the techniques even if not in a pure functional language.
Overall, the arguments presented by Bertrand Meyer were persuasive in some ways, yet lacking in others. Unable to cite more real world applications leaves me wanting. Without this, the modularization issue cannot be addressed in full. Other areas such as insisting on Command-Query-Separation as a default falls largely on deaf ears outside of the Eiffel community. I do realize the more progressive teachers are starting to focus on this, but for the most part, CQS is largely untaught. Instead having the type system express our side effects is a much more powerful option. Overall, I thought a lot of the arguments given his bias and the lack of better examples, fell flat. Using Eiffel, a non-mainstream OOP langauge doesn’t help his case and should have focused on more mainstream approaches as in C#, and Ruby among others.
So, who wins? That’s a good question. If you’re in a world resigned to side effects then OOP might just be for you. Else, Haskell and the lessons learned from the language can be powerful instruments in creating rich and beautiful architectures using pure functional techniques. I feel with time and maturation, the functional programming community can contribute more to the architecture picture. The battle continues?