There is a lot of code in this post, it will probably not compile "as is" because of missing headers or compiler's incompatibilities. Nevertheless, we made available through our open source initiative the full working library on github. It has been tested on Clang 3.1, gcc 4.6.1 and MSVC 11 on FreeBSD, Linux and Windows.
Functional languages are built around function composition. However, function composition isn't limited to functional languages as every time you chain at least two calls you are actually composing functions. Although Eric Niebler already wrote on the topic of function composition in C++, in this post we'll go further and enjoy the availability of C++11 compilers for a more hands-on approach.
At Bureau 14 we use function composition a lot as our software, quasardb, is built on the principle that each treatment is composition of simple functions with a single responsibility. For example, when you add an entry to the database, it is chained call of receive, decode, store, check, build reply and send. In other words, it could be written as send(build_reply(check(store(decode(receive()))))); (I hope you stored enough parentheses for winter because winter is coming).
Our mission is to build the fastest database on Earth and we want to be the fastest without the shadow of a doubt. It was easy to crush engines built in Java or similar languages (Cassandra, Coherence...) but topping engines written in C/C++ was harder (MongoDB, memcached, redis...) and required to be obsessive about design and implementation.
Function composition is one of the tools we used to achieve an out of this world level of performance. Function composition enables the compiler to be as clever as possible with optimizations while being a solid yet modular foundation upon which you can build highly parallel software.
Having code truly parallel is not something that is easy to do generally speaking but having the most complex parts of the software (for example automatic replication and failover) expressed as a series of simple functions helps you see where your code is multi-threaded challenged. Our approach makes parallelization a matter of accessing lock-free structures or duplicating input data.
Last but not least, writing tests and creating mock-ups is much, much easier thanks to our functional paradigm.
As great as function composition may be, in C++, it can quickly become cumbersome and inelegant to write. For example, let's say you have a class called server (by the way, at Bureau 14, having short variables names is our way of being green):
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class server
{ public: private: }; |
What's wrong with this code?
Well, it's absolutely not generic, but you could argue that at some point you have to specialize. Nevertheless, if we want to add a function, or, if for some process we need to specialize some, it can quickly be error prone. In addition, we may probably want to build the kernel object around the same logic: it would therefore be better to have a common framework to describe such objects.
Wouldn't it be great if we could write:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
template < typename ... Processors >
class server { public: private: }; |
Implementing a server is now just a matter of writing:
1
|
server <receiver, decoder, kernel, encoder, sender > s ;
|
Need to add encryption?
1
2 3 4 5 6 7 |
server <receiver,
decipher, decoder, kernel, encoder, cipher, sender > s ; |
Since the kernel is built around the same logic, this will also enable us to write:
1
2 3 4 5 |
server <receiver,
decoder, kernel <validation, cache, persistence >, encoder, sender > s ; |
And to give you a glimpse of what it actually looks like in our production code:
1
2 3 4 5 6 7 8 |
server <receiver,
decoder, node <chord, kernel <validation, cache, persistence >>, encoder, sender > s ; |
More generally, we also want to be able to use regular functions and combine them easily:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
data f (data )
{ // some processing } data g(data) data h(data) data process(data) |
In this post, I'm going to show you how we can write exactly this in C++ 11.
Composing two functions is the trivial case and will be our starting point.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
template < typename Inner, typename Outer >
class composed { public: public: private: }; |
As you can see, a composed functor takes two functors as parameters and executes them when the overloaded parenthesis operator is called. With the help of a function we can make it a little bit more usable:
1
2 3 4 5 |
template < typename Function1, typename Function2 >
composed <Function1, Function2 > compose (Function1 f1, Function2 f2 ) { return composed <Function1, Function2 > (f1, f2 ) ; } |
And now we can write:
1
|
compose (f, g ) (data ) ;
|
Provided that f and g are instantiated functors. As composed is a functor itself, we can even write:
1
|
compose (compose (f, g ), h ) (data ) ;
|
Note that you can store the composed functor for repeated use:
1
2 3 4 5 |
auto c = compose (compose (f, g ), h ) ;
c(data1); |
You can even use regular functions thanks to decltype:
1
|
compose ( ®ular function, functorconstructor ( ) ) (data ) ;
|
This is all well and good but we need to push this further. We want to write compose(f, g, h), not compose(compose(f, g), h). A straightforward, albeit not very elegant, way is to write all the overloads:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
template < typename Function1, typename Function2, typename Function3 >
typename composed < typename composed <Function1, Function2 >, Function3 > compose (Function1 f1, Function2 f2, Function3 f3 ) { return compose (compose (f1, f2 ), f3 ) ; } template<typename Function1, |
I don't know for you but I'm already bored to death, and I only wrote the four parameters version. Why write a dozens of overloads when you could be killing aliens in X-COM?
Or we can abuse the preprocessor. Yes, I know, macros are evil, you should never use them, every time you use a macro a kitten dies, yada, yada, yada. Now that the cat is out of the bag, let's do crazy stuff with macros.
Thanks to Boost.Preprocessor, we can write recursive macros in a convenient way: this will make it possible to safely write wrappers for as many parameters as we want:
1
2 3 4 5 6 7 8 9 |
#define DETAILCOMPOSEFUNCTION(z, n, )
template <boost_pp_enum_params_z(z, boost_pp_inc(n),="" typename="" function)=""> compose(BOOST PPENUMBINARYPARAMSZ(z, BOOSTPPINC(n), Function, f)) -> decltype(compose(compose(BOOSTPPENUMPARAMSZ(z, n, f)), f##n)) { return compose(compose(BOOSTPPENUMPARAMSZ(z, n, f)), f##n); } BOOSTPPREPEATFROMTO(2, 20, DETAILCOMPOSEFUNCTION, nil) |
I never said that the macros were going to be understandable or easy to read, but they do the job and they do it in a minimal amount of code. More importantly they reduce errors due to typos to zero: once your macro is right, all the generated functions will be correct.
How do the macros work? Remember we already have the 2 parameters function defined: this is going to be our recursion termination. We start with 3 parameters (we specify 2, but we use BOOSTPPINC()) and stop at 20. We then generate the template and functions parameters with BOOSTPPENUMPARAMSZ() and BOOSTPPENUMBINARYPARAMS_Z(). Since we generate the function incrementally, each new version is defined as the recursion of the previous one, exactly as we did when we wrote the 3 and 4 parameters versions.
Our recursion works in a "pop back" fashion, that is, we build the composition in appending the last parameter and recurring on the remainder, on the left. It has to be done in this order as we want our composition to work from left to right: when we write compose(f, g, h), we want f to be executed first, we want h(g(f(x))). We could have a more functional semantic where compose(f, g, h) would actually be f(g(h(x))) but this would be confusing as in C++ we write calls from left to right (Remember Ghostbusters: don't cross the streams).
For example when we write compose(receive, decode, process), we expect to receive, decode and finally process. Writing compose(process, decode, receive) wouldn't be very C++ish.
For a given list of parameters [h:t] where h are all the elements except the last one and t is the last element, the recursion is defined as:
1
|
compose (compose (h ), t )
|
Until we reach a list of two elements [a, b] where compose becomes:
1
|
compose (a, b )
|
But Edouard, we are writing C++ 11! Why don't we use variadic templates?
Variadic templates unfortunately work in a "pop front" logic, making the code actually much less straightforward (because these unintelligible recursive preprocessor macros are obviously mundane). You could nevertheless write a pop front variadic template version with an adapter that reverses the list of parameters.
All is good and well but we still can't write:
1
|
compose (std :: make_tuple (f1, f2, f3 ) )
|
which is a shame because it's what we really need for generic programming.
The first order of business is to somehow be able to know the right "composed" functor type given a list of types. We'll work with Boost.MPL and more precisely boost::mpl::vector to list our types.
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
template < typename Types >
struct make_composed { // the size must be at least 2 typedef Types types ; // we need first and second element // we need the subset without the first and second element typedeftypename boost::mpl::fold<typestail, |
Of course this will not compile if the size of your MPL vector is less than 2 (pretty hard to combine less than 2 functions, isn't it?).
If you want to compose functors of type receiver, decoder, kernel, encoder and sender you can have the final composed type in writing:
1
2 3 4 5 |
typename make_composed <boost :: mpl :: vector <receiver,
decoder, kernel, encoder, sender >> :: type |
Until now we didn't have the need for this because we used the C++11 decltype feature, but as we will use a Boost.Fusion vector to carry all our parameters listing the types in a MPL structure will become necessary. Why do we want to use a Boost.Fusion vector? We need a meta-programming friendly structure to work on the parameters as we build the composed functor. Boost.Fusion vector is exactly that and will enable us to write the meta-programming equivalent of our satanic macros.
Boost.Fusion is a library one may sum up as compile-time introspection through meta-programming. With Boost.Fusion you can create multi type containers, associate values with types, transform structures into containers, iterate on structures' members and much more!
Without further ado:
1
2 3 4 5 6 7 8 9 10 |
template < typename Vector, int Size >
struct make fromvector : make composed<Vector> { type operator()(Vector v)const { staticassert (Size > 1, "need at least 2 functions to make a composition" ) ; typedef typename boost :: fusion :: resultof ::asvector < typename boost :: fusion :: resultof ::popback <Vector > :: type > :: type head vector; return type(makefrom vector<headvector, head vector::size::value>()(boost::fusion::asvector (boost :: fusion :: popback (v))), boost::fusion::back(v)); } }; |
We can't use decltype anymore because of a chicken-egg problem: we would need to know the result of the recursion we're currently building. That's why we use makecomposed for the return value of makefromvector.
The functor takes a Vector type (hopefully a Boost.Fusion vector, but any RandomAccess Boost.Fusion container will work) and split it in two, exactly like our macro did. We pop the last element and recurse on the remaining vector. We therefore need to define the recursion termination, which can be written as:
1
2 3 4 5 6 7 8 9 |
template < typename Vector >
struct make fromvector <Vector, 2 > : make composed<Vector> { type operator()(Vector v)const { return type(boost::fusion::at<boost::mpl::int < 0 >> (v ), boost :: fusion :: at <boost :: mpl :: int_ < 1 >> (v ) ) ; } } ; |
We're almost there! All that is left to do is to convert a tuple into a Boost.Fusion vector:
1
2 3 4 5 6 7 8 9 10 11 |
template < typename Tuple >
typename make composed< typename boost::fusion::resultof :: asvector <Tuple>::type>::type compose(Tuple t) { // convert the tuple to a Boost.Fusion vector typedeftypename boost::fusion::resultof :: asvector <Tuple>::type vectortype ; return make fromvector <vector type, vectortype :: size :: value > ( ) ( boost :: fusion :: asvector (t)); } |
We can now happily write compose(std::maketuple(f, g, h))!
Thanks to copy elision, the compiler can avoid a lot of unnecessary copies. For functions or simple functors this is even not a problem as the copy itself may be negligible, but should you use larger functors or functors that cannot be copied this becomes an issue. Fortunately, in C++11 rvalues references and perfect forwarding enables us to make sure no copy is ever made.
If you're new to rvalue references, I strongly advise you to read this article by Hinnant, Stroustrup and Kozicki.
The difficulty with perfect forwarding is that you have to make sure the forwarding chain is never broken. To do this, we will use our library with a functor that cannot be copied but supports move semantics:
1
2 3 4 5 6 7 8 9 10 11 12 13 |
struct add_one
{ int operator ( ) ( int x ) const { return x + 1 ; } } ; struct addonenocopy : addone, boost::noncopyable |
If we call:
1
|
int r = compose (add oneno copy(), addone nocopy ( ) ) ( 2 ) ;
|
We will get an error. We will use this error to implement perfect forwarding all the way down.
The first thing to do is to modify to the compose function:
1
2 3 4 5 6 |
template < typename Function1, typename Function2 >
composed <Function1, Function2 > compose (Function1 && f1, Function2 && f2 ) { return composed <Function1, Function2 > (std :: forward <Function1 > (f1 ), std :: forward <Function2 > (f2 ) ) ; } |
It nevertheless will not compile yet as the constructor of composed has to be changed as well:
1
2 3 |
explicit composed (Inner && inner = Inner ( ), Outer && outer = Outer ( ) ) :
_inner (std :: forward <Inner > (inner ) ), _outer (std :: forward <Outer > (outer ) ) { } |
And to have composed fully support perfect forwarding, we need to add move support via a new constructor and a new assignment operator:
1
2 3 4 5 6 7 8 9 10 11 |
composed (composed && other ) :
inner(std::move(other.inner ) ), outer(std::move(other.outer ) ) { } composed & operator =(composed && other) return*this; |
Now the first call compiles!
Since we want to support more than two parameters, we need to modify our macro. This modification isn't as trivial as it may sound because of the forwarding:
1
2 3 4 5 6 7 8 9 10 11 |
#define WRPMEDETAILFORWARDSINGLE(n) std::forward <function##n> (f##n)
#define WRPMEDETAIL FORWARDSINGLE COMMA(z, n, _) ,WRPMEDETAIL FORWARDSINGLE(n) #define WRPMEDETAILCOMPOSEFUNCTION(z, n, _) |
This forces us to add intermediary macros to properly forward the arguments to the next level of recursion.
What about tuples? Unfortunately Boost.Fusion does not fully support perfect forwarding yet, which makes it impossible, for now, to have perfect forwarding through the tuple interface (unless you write one without Boost.Fusion). I'll post an update as soon as this changes.
I hope you enjoyed this post. If you'd like to use the composition paradigm in your own program, feel free to use our BSD-licensed library! It is tested against VC11, gcc 4.6.1 and clang 3.1 on Windows, Linux and FreeBSD. Fetch it on github!