by Zoran Horvat
There is an ongoing dispute on the Internet about which kind of programming, functional or object-oriented, is better. Large areas of the scene are polluted by a raging war where bigots of all kinds are serving fabricated “proofs” to disregard the opposing programming technique. Notably, an extremely loud and aggressive minority exists, which claims that functional programming is the only real kind there is, while object-oriented programming is a bad thing.
That view severely hurts programmers who wish to learn and understand programming rationally. This article is an attempt to shed light on that artificial divide. As we progress through the list of programming concepts, a higher truth will emerge on its own: Whatever code there is in a functional language, exactly the same code segment can be constructed in a proper object-oriented language.
With that understanding in mind, it becomes clear that functional and object-oriented programming can be viewed as isomorphic variants of one unified software design method.
In this article we will walk through the basic elements of functional programming. For each of them, a corresponding object-oriented syntax will be given. Don’t fear that that will require forceful fitting of an unnatural design into an object-oriented language. Quite the contrary: All fundamental elements of functional programming are also the fundamental elements of object-oriented programming.
Function is the basic element in programming – functional and object-oriented the same. Mathematical function is a mapping from one set to another set.
In functional languages, you would apply the function in the same way you would do that on paper: f(x). In F# in particular, there is the pipe-forward operator by which you tell that x should be passed to this function f on the right, so that f can apply itself to x and produce the result.
// F#
let y = f(x)
let y = x |> f
That is how you call the function in a typical functional language. What does that look like in an object-oriented language? There you call a function on an object. But that is only syntax. The essence is that the value, the object on which the function is invoked, will be passed as the implicit this, and you will end up with f(this).
var y = x.f() // C#, Java… f(this)
let y = x |> f // F#
That is only the way to call a function on an object in spirit of object-oriented programming.
Therefore, we conclude that functions are observed the same in functional and object-oriented languages. The syntactic difference is the result of object-oriented view on objects as stateful elements which are bundling state and behavior together.
However, if we insist on state encapsulation, then only behavior, i.e. functions, remain accessible to the caller, and in that respect functional programming is no different than object-oriented programming.
The next concept, the critical one in functional programming, is closure. In functional code, if you have a function, like scale in the picture below. The function’s body can also use so-called free variables.
Not everything must be specified through the function arguments. However, free variables still must come from somewhere, and in functional languages that is called the environment. Free variables are captured, copied from the environment when the function is declared.
// F#
let k = 2
let scale x = k * x
...
let y = scale 3
Later, much later, maybe on a different thread, maybe in a different function, somebody will invoke that function scale(x). At that moment, the captured value of the free variable will be effectively used.
All functional languages support this in syntax. But there is also the equivalent to this in object-oriented languages, as the next picture demonstrates.
Environment is the object of a class there. Free variable is the object’s field, and function accesses that field via the this reference. Therefore, you always have closure in any object-oriented code, whether you planned to have one or not. Every object is the closure.
Formally, when lambdas were introduced in C#, they were closures, and you could use free variables in lambdas in C#. Lambdas are implemented as classes. Compiler generates a class on your behalf.
// C#
var k = 2;
Func<int, int> scale = x => k * x;
...
var y = scale(3);
Free variables are declared as fields in the generated class, and when you effectively assign a lambda to a delegate, an object of the generated class is instantiated. Therefore, even formally, in object-oriented languages an object is a closure.
The next important principle is currying. That is the way to reduce the number of arguments.
It is always your aim in programming to reduce the problem dimension. Whatever you write, you wish to reduce the problem from many variables to not-so-many variables, so that you end up having a no-variables problem.
To be precise, no variables doesn’t exist in object-oriented code, because there is always that implicit this reference. Through problem decomposition, your target is to reach a single parameterless function on a single object, which you simply invoke.
In functional languages, currying is the tool to that end. If you had a function with two arguments, x and y, and supposedly you know x, but not y, then you can fix x, and apply the function to a subset of arguments.
The result of that application will not be the value, simply because you haven’t provided all the arguments. The value cannot be calculated, so the result will formally be a new function, which receives those arguments that remained.
// F#
let f x y = x * y
let g = f 2
...
let z = g 3
Currying is the principle, and it is important even if you are not writing functional code. Simply, once you see this, and once you understand it, you start viewing the world with new eyes and you see that function arguments are not just a mindless request to supply their concrete values. Not like “Give me these five items” – No, it’s more like: “Can I give two of them, and let someone else provide the remaining three later?” That idea turns into a design principle.
Later, someone will receive the g(y) function (as in the code segment above), which results from currying, internally fixing the value of x to two, and that someone will provide the second argument, y. If they called g(3), they will be given the result 6.
Functional languages support currying natively, at the syntax level, while object-oriented languages don’t. The code example above demonstrates how F# lets us define the function f x y with no parentheses, no commas. There is the sequence of arguments, and then the caller can supply only some of them.
In that light, f 2 results in a new function, g. When g 3 is applied later, the result will be the value 6.
This shows how easy it gets to reduce the argument list in a functional language.
What is the corresponding element in object-oriented languages? There the function f doesn’t have two, but only one argument. The other argument – actually, the first argument – is the implicit this reference.
res = x.f(y); // C#, Java...
What does this function f return? It would be difficult to return a one-argument function. That would have to be a delegate, and that would needlessly complicate code, reducing its readability.
Instead, you return an object, and that object would then expose a method with the remaining argument. This is the design principle in object-oriented programming, which is by all means equivalent to currying.
As a side note, let me tell that people make terrible mistakes in object-oriented programming when they construct objects that make miracles, that expose huge functions, objects that know and do everything. You don’t do that.
Every object should be a factory for other objects. Let it do one simple thing, make one simple transform, and then create an object which knows the rest, everything that is beyond its scope.
// C#
var temp = x.f();
var z = temp.g(y);
This is the introduction to fluent API. But before we arrive at that, let me show you a special case of currying, which you are applying all the time.
Even in a multi-argument function, say, in a functional language, not all the arguments are the same. What are the function arguments anyway? Those are the parameters to the algorithm. You are parameterizing the object’s behavior.
Whether this function is stand-alone, in a functional language, or a method defined on an object, arguments are there to parameterize the algorithm. Well, not all the parameters are the same.
Some of them are behavior, while others are values. The greatest power in programming today is parameterizing with behavior, rather than with values. Would anyone want to collect 15 values just to pass them to a function, and pray for a result? That would be madness, that is what programming was twenty years ago, maybe.
Today, you feed objects with behavior. You parameterize an object and tell it: Hey, object, execute one little piece of the algorithm. Here is your x, that is all you need, that is that single degree of freedom of yours. The rest is behavior for you to use for the rest of the algorithm.
That object then becomes very simple, as it doesn’t know what the rest of the algorithm is. Functions in functional programming, as well as methods in object-oriented programming, all turn into algorithm components, so that you can compose them into a large algorithm, a complex transform.
// F#
let f p q x = p( q(x) )
let g = f p q
...
let y = g 3
// C#
class A
{
private D dep1;
private E dep2;
...
public int g(int x) =>
this.dep1.p( this.dep2.q(x) );
}
var a = new A(d, e); // same as g = f p q in F#
var y = a.g(3); // same as y = g 3 in F#
Those behavior parameters, those are the dependencies. In functional languages, you would apply currying to fix the functions in a function, so that you receive the new function which only has a value parameter. You can pass that function around, give it to someone else, who is oblivious of the origin of that function. But that someone possesses the value argument, and it only calls g(x), oblivious that behind that simple call the whole avalanche of behavior ensues.
The corresponding mechanism in an object-oriented language is also given in the code sample above. In object-oriented languages, you also parameterize with behavior, plus with the common argument values, as needed. This time, behavior are objects, not functions anymore, even though you are also free to use lambdas as parameters. Essentially, lambdas are also objects.
So, you always supply an object which exposes a function, and that object’s public behavior will be used as part of the algorithm. You would normally fix those objects through the constructor, and then simply call the public method, supply value parameters and collect the result later.
Dependencies are encapsulated in object-oriented programming. Why would you want to encapsulate dependencies? It’s because the one who invoked g(x) on an object doesn’t depend transitively on what the object depends.
Those transitive dependencies would complicate matters, they are wreaking havoc in programming, causing needless complexity – that happens when you start depending on what your third parties depend. When dependencies are externally visible, that is a leaky abstraction. It is dangerous because when others see your dependencies, they start assuming your concrete implementation and start implementing own responsibilities knowing that your implementation supports that.
Constructor is not part of the public interface, so nobody will ever know what your class depends on. That makes dependency injection a subset, a special case of what currying is in functional languages.
The last concept I wanted to mention, function composition, is also extremely important, and it explains how you develop software. Preceding concepts were software elements. Composition of functions is your entire program.
Composition means that you have a mapping from X to Y, and a mapping from Y to Z. Well, you are aware that there are two mappings, but who else would care to know that? You can cover the middle segment with a rug, and nobody will ever see it. What they will see is a mapping from X to Z.
You can construct complex behavior by composing functions internally, while from the outside it only looks like end-to-end mapping.
This may sound abstract, but here it is:
// F#
x |> f
|> g
|> h 2 3
|> p i j
|> q
In functional languages, function composition is supported natively, because that is the essence of functional languages – constructing functions by composition. You would pass x to some function f, which produces the result. Well, that result is in fact an invisible intermediate result.
It is sent to function g, which also expects one argument. Its own result is passed to the next function, h. Here, this function h, how many arguments does it receive? Three arguments, but we have fixed two of them – that is currying in action!
Currying will produce a new intermediate function receiving one argument, and intermediate result will be passed to that function.
Then we construct a closure, capturing i and j variables from the environment, which turns all this to another single-argument call. Its result will be passed further.
All this together just maps x into some final result. Only internally is that implemented as a very complicated composition of a bunch of libraries, dependencies that the one who called the composed function doesn’t even know exist.
What would this same code look like in an object-oriented language? Can you see it already? Here is how it goes.
// C#, Java...
x.f()
.g()
.h(2, 3)
.p(this.i, this.j)
.q();
In an object-oriented language, you call the function f on the argument x – that is the object. It returns the new object which exposed function g. That function returns another object exposing h with two arguments.
Fix the arguments to obtain the next object in line. This one exposes function p, but its arguments will now be captured from the closure, from the object inside of which this whole code executes. Take its this reference and copy the values of fields i and j into the function p, making the entire call a closure.
This brings fluent API to the table, the most powerful mechanism in object-oriented programming which lets you construct enormously complex functionality. The idea is based on problem decomposition. Decompose, decompose, decompose, until all objects become trivial.
Every function will only return the next object, and that is it – the sole purpose of every object is to be the factory for the next object in chain.
Yet, you won’t make a clean profit with that principle – in fact, you will be at an immediate loss, because you had to create a new class, to design it, think of its responsibilities, think of its name – that dreaded question “how we’re gonna name that class; how its function?” There’s a lot of new problems now.
But, once you overcome them, you will see – it’s not your software to write this composition. Your software is to write similar compositions a thousand times. That is where you will start noticing that the same concept jumps out in another place, and another place, and another.
In the end, the same kind of object, say, that one at the beginning, exposing g, will be constructed from several different contexts. And your software will eventually turn into a so-called deep domain model. The concepts will be split precisely, and you will combine them in a hundred, a thousand ways.
These were the concepts, programming elements. Not elements of the language and syntax, but elements of programming. There were functions, closures, currying, and composition.
You might add a couple more, but those would be the derived concepts.
When you look back at how C#, Java and similar object-oriented languages were developing over years, then this is what you will see. These concepts are supported with more and with deeper syntax elements.
Hence, programming in an object-oriented language today doesn’t mean you have to write object-oriented code. You don’t have to!
You are free to write functional code and develop fully functional designs in object-oriented languages. Alas, when syntactic support is missing, then you will type a lot more code, and complicated code, and you will fail to see the essence.
That is where lots of room for improvement remains. Modern object-oriented languages are adding new syntactic elements to support proper functional programming.
As a conclusion to this analysis, I will settle down with this final thought. It is not object-oriented programming that prevents fluent functional programming and design. It is the lack of syntax that makes it needlessly complicated. In recent years, we have witnessed a strong movement towards making mixing paradigms – functional and object-oriented – easier in major traditional object-oriented languages. If there is any certainty in the programming world, then we can be assured that that trend will continue.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.