Design by committee (DbC) was never meant to be a compliment. In DbC, every decision has to go through a debate and be subjected to a political process. Because of the needed compromise among the parts, the resulting artifact usually tends to be clunky and patchy, lacking elegance, homogeneity, and vision.
C++ is quite an old language, not as old as its parent C, or other veterans of the computer language arena, but the first standard is now 23 years old and the language was quite well-formed and aged to mature its dose of quirks, long before that.
The problem with committee-driven aging is that it is not a sound recipe for aging well, quite the contrary. Features add up like cruft and features are randomly incorporated. I’m not saying that C++ standard committee has not done a great job, but the committee has the goal of producing compromises on proposals, weighing in all the considerations about compatibility with legacy code. While sometimes, the best interest for the language is in the direction of sacrificing some compatibility (see Scala 2 -> Scala 3, Python 2 -> Python 3), or throw away and restart choices that showed their shortcomings. See also this blog post.
C++ claims to be a general-purpose language that supports multiple paradigms. And functional paradigm is gaining quite a momentum among the programmers’ community. So, in this blog series, I’m going to investigate whether C++ is ready for being used as an FP language or not.
What is functional programming? Well at its basis (at least as I understand it) functional programming is “programming with functions”, where functions are intended in the mathematical sense. A mathematical function sets a relation between its arguments and its result. It may not sound very computer programming, but it is enough to define a Turing-complete programming language. Believe it or not, be reassured, because many smart scientists have demonstrated it is the case.
Before delving into C++ FP magic world, I need to make some introductory talk, to lay out the work of the future posts.
A mathematical function has no “side-effects”, meaning that all its “code”+ is dedicated to producing the returned result. Typically printing to the terminal, drawing onto the screen, or sending bytes over the network are all side-effects.
If you manage to write functions without side effects, then they have an interesting feature – referential transparency. That is, you can literally substitute one function in place of its invocation and the program preserves its correctness (or lack of thereof 🙂 ). This characteristic is interesting both for writing parallel algorithms, for compiler writers, and for component reuse.
In order to provide referential transparency, you cannot change shared variables (otherwise you are changing the behavior of other functions from one call to another). This is a very desirable property when working with parallel computing – no change in shared data means no arbitration mechanism and therefore no locks and no critical runs.
Compiler writers may take advantage of caching function results, since the same function, invoked with the same arguments will always produce the same result. Also, inlining the referential transparent function is always safe regardless of the order of invocation.
Component reuse benefits from referential transparency because functions and their dependencies do not interfere with the state of the program.
Eventually, RT helps the programmer in writing or understanding code, since everything is local to the function and she doesn’t need to take into account… spooky effects at a distance.
Another important feature functional programmers are accustomed to finding in FP language is the monad.
Monads are building blocks of the FP paradigm just like the Object base class is the OOP building block.
A Monad can be considered a generic interface for wrappers of type T. Several monads with different purposes exist, but all of them have the same interface and can be chained together using such interface.
The interface is composed of just two methods – the constructor and the flatMap.
The first method is the constructor. Given an object of type T, the constructor builds a monad of type T, wrapping the given object.
flatMap is a transformation method. You use
flatMap to produce another monad of the same kind, that wraps another type instead of T.
flatMap takes a function from T to a monad of a generic type U (that can still be T, but it is not needed).
This is an extremely brief description of what a monad is, so if you are confused, it’s pretty normal. For a thorough, but not too formal description have a look here. It is not C++-related, but it is pretty general for introducing the concept.
Functional by Libraries
In this series, I’ll try to analyze C++ language regardless of libraries. In the end, you can do everything in every language, provided the right library. But when you push this concept too much, the library becomes a sort of interpreter for a metalanguage providing the paradigm X to the underlying language. The language is restricted to the role of glue and the farthest is from X the easier is to add bugs in the glue.
There are two additional considerations – first using libraries for even trivial tasks is daunting, as everyone that tried OOP in C, knows; second, the compiler may get confused about programmer intention and miss optimization opportunities.
I’ll try to better explain. When you write:
It may not seem that different from using a function that sums its parameter and write:
To the compiler it is entirely different – in the first case, it knows that you want to sum two numbers, and it can reuse the result if it has already been computed, or take shortcuts if by analyzing the previous code, it is able to assess the value of one or both the sum operands.
When a function appears, first the compiler checks if the definition is known or not, if the definition is unknown, then there is no choice but to call it. Even if the definition is known, sometimes there is no other way.
So, when the language provides direct support for a paradigm, the resulting code is always more performant (and usually programmers’ life is happier).
That’s all for this rather lengthy introduction. The next post will be about functions as first-class citizens and C++.