FP vs. OO: Simplicity — Fighting complexity at all costs
In this post, Joy Clark, a consultant at innoQ, claims that simplicity is key to creating and maintaining good software and explains why the functional programming approach offers a great way to achieve it.
My colleague recently wrote an excellent post discussing the functional and object-oriented paradigms. As someone who comes from a functional programming background, I definitely agreed with one thing: We need to stop building arbitrary walls that prevent us from learning from and helping each other.
The post made me dig deep down within myself and really consider why I love functional programming so much.
In essence, the functional and object-oriented paradigms stem from different concepts about how the world works. They represent different opinions about how to best model the world while we are writing programs.
The object-oriented paradigm models everything as an object. These objects can then interact with each other and thereby update their internal state when something in the system changes. This corresponds to how we often conceptualize the world, for instance, by modelling a computer mouse as an object whose position on a screen changes over time.
The functional programming paradigm takes a different approach. The behavior of the system is modelled using pure functions and these are strictly separated from the actual data within the system, which is modelled with immutable values. This is based on the mathematical perspective that a value itself cannot change (i.e. the coordinates
x,y are constant) and all changes in the system are actually the application of a function (i.e. the movement of a mouse can be represented with a function that takes the coordinates
x,y and produces new coordinates
The question then remains: Which approach is better?
My answer: This is the wrong question.
I personally have been very influenced by Rich Hickey’s video about Simplicity. We as humans are not very good at understanding or comprehending multiple things at any given time. We can follow one idea or concept, but as soon as multiple ideas or concepts come into play we easily lose our focus and get confused.
Simplicity is key to creating and maintaining good software. Even when programming a small web application, it is not possible to examine every part of the software at any given time. Instead, we begin inspecting the software and try to figure out the behavior of the application step by step. A point in the program where unrelated concerns are mixed together ‘complects’ the entire program and thereby makes it more complex. By attempting to remove or minimize these positions in our program, we increase the simplicity of the application and make it easier to maintain in the future.
Simplicity is not easy.
It is easy to write down the first thing that pops into your head. It is easy to take shortcuts, to be lazy. It is not easy to refactor your code to the point where it is clear what each line in the program does. It is not easy to create a code basis that you can look at five years later and still think ‘Oh, this does that. That makes sense’.
In my opinion, the functional programming approach offers a great way to achieve simplicity. A small function with a single job is simple. The data which serves as input for the function is also simple. The result which is produced from executing the function is also simple. When these three elements come together in the program execution, I can therefore still comprehend each individual part. With functional composition and higher order functions these simple components can become much more powerful without losing their simplicity. By breaking a program down into composable functions with a clear purpose, complexity can be minimized.
Object-orientation deals with complexity by encapsulating it and providing a meaningful abstraction of the problem for the user. This a powerful tool as long as it is possible to ensure that all of the data really has been encapsulated in the desired scope and is not able to be modified from an outer source. However, since immutability is not the default, it is difficult to ensure that objects are only modified within the correct scope. For instance, as soon as a list is exposed in Java, this list can be modified by a subclass or an external library which would produce unexpected behavior at a later point in the program execution. This adds an element of complexity to the language which would not be there in a language where all data is immutable.
This does not mean that a program written in an object-oriented programming language is inherently complex.
I personally am a functional programming enthusiast who spends an enormous amount of her time writing Java programs (a typical object-oriented language). However, I still attempt to make my code as simple as possible by using the Java 8 Stream API, by eliminating mutable state as much as absolutely possible (hello
final keyword!), and most of all by refactoring and rewriting and reviewing my code over and over and over again until I am satisfied with the result (and then I will probably go over it again).
It is also not true that just because a program is written in a functional programming language it is automatically simple.
Personally, I love the Clojure programming language because I think in a functional way and the language allows me to write out a program exactly how I have formulated it in my head. However, one of the critiques I have heard about the language is that the entire state of the program is often saved in one huge global map which is then given as an argument for many functions within the program. If you want to change the structure of the map, you then have to update all of the functions which take this structure for granted.
This criticism is justified because having to know the structure of the map has complected it together with all functions that accept it as an argument. My first step would always be to try to eliminate as much of the state as absolutely possible. If the remaining state is too complicated, we can add a level of abstraction by using one of Clojures datatypes like records or writing a single function responsible for updating the global state that then delegates to smaller functions. This is similar to object-oriented encapsulation apart from the fact that immutability is not sacrificed.
We live in the real world, and often the task at hand is really difficult. There is some essential complexity that we will never be able to get rid of. However, we should strive to minimize the amount of unnecessary complexity present in our applications regardless of the programming language we are using.
Simplicity requires effort. So let’s get started.