Harder to C++: Monads for Mortals [2], First implementation
This blog post is part 2 in a series. Part 1 is a general introduction. This part discusses a first implementation of the monad in C++.
There are two fundamentally different approaches to implementing monads in C++. Let’s first discuss the approach we will not pursue.
A Way We Will Not to Implement a Monad in C++
The C++ template mechanism is a full functional language (this discovery, by Erwin Unruh, was a bit of a surprise). As a consequence C++ templates are sometimes used for template meta programming: programs can be written using (only) C++ templates and are as such evaluated / executed, at compile time. There are not many practical uses for template meta programming. One practical use was to unroll (small) loops in order to gain performance. This gain, however, can nowadays also be obtained by modern, optimizing C++ compilers.
The discovery that the C++ template mechanism constitutes a functional language, also has lead some people to implement monads in a template meta programming based functional language. Examples are the Cee Plus Plus Monad Example, and Implementing Monads for C++ Template Metaprograms. However, these implementations live in a part of C++ that is little used. Moreover, the required syntax is complex, which makes it unattractive for general use.
The above approach and the goal pursued here are, I think, trying to answer different questions. The above examples seem to try to mimic functional programming with C++, and implement monads in that context. Contrary to bringing C++ to functional programming, I want to know if bringing the functional programming construct ‘monad’ to C++ will provide me with an advantage, and to what cost.
Below we will start with a simple implementation of a monad in mainstream C++ and show that it is indeed a monad. Then we will see what we like or dislike. Note that we will not be doing any functional programming, which, in fact, is quite in the spirit of Eugenio Moggi’s intentions.
A Simple, Mainstream C++ Implementation of the Monad
Implementation starts with some includes and a using statement. The first important construct is the monad type constructor, or rather: the C++ template.
#include <iostream>; using namespace std; // monad type constructor template<typename V>; struct monad { // Wrapped value V value; };
We saw in part 1 that the result of the unit function has the semantics of a computation. As you can see, this C++ class / struct template does not have any computations, it is a trivial computation. For one thing, we could add a function application operator to it ( operator() ), that would perform some non trivial computation, and we might indeed at some point do so.
The next item to define is the unit function, the factory:
// factory: unit function template<typename V> monad<V> unit(V val) { return monad < V > { val }; }
It is simplicity itself. An alternative could have been to implement the unit function as the monad struct constructor. That would have been more efficient, so we will do that in a number of following implementations.
I have given the bind function the form of an overload of the bitwise OR operator ” | “.
// Bitwise OR operator overload: bind function template<typename A, typename R> monad<R> operator| (monad<A>& mnd, monad<R>(*func)(A)) { return func(mnd.value); }
I got the idea to use an operator instead of a function called ‘bind’ from the CeePlusPlus example referred to above. However, that code overloads the ” >>= ” operator, in order to make the code resemble Haskell somewhat. I prefer the ” | ” because it is the same symbol as the Unix / Linux shell pipe operator. And indeed, we will see shortly that in function composition, bind can well be considered to pipe results from function to function.
The above bind function first ‘unwraps the value’, by referencing the value of the monad object, it then applies the function ‘func’ to it, and returns the result wrapped in a new monad template object.
At this point we have a first implementation of a monad! Note how very small and simple it is. Let’s add some functions and give it a spin.
// divides arg by 2 and returns result as monadic template monad<int> divideby2(int i) { return unit<int>(i/2); } // checks if arg is even and returns result as monadic template monad<bool> even(int i) { return unit<bool>(i % 2 == 0); } int _tmain(int argc, _TCHAR* argv[]) { // Wrap 22 in monadic template auto m1 = unit<int>(22); // Apply bind 4 times in function composition auto m2 = m1 | divideby2 | divideby2 | divideby2 | even; cout << boolalpha << m2.value << endl; cin.get(); return 0; }
Note the elegant function composition offered by the bind operator.
Minimal Verification that Monadic Laws Hold
By means of a single example we will verify that the monadic laws (see part1) hold:
int _tmain(int argc, _TCHAR* argv[]) { // Wrap 22 in monadic template auto m1 = unit<int>(22); // Left identity // First explicitly convert to function pointer (because of template) auto pf1 = &unit < int >; // then apply bind operator auto m2 = m1 | pf1; cout << "Left Identity result: " << m2.value << endl; // Right identity auto m3 = m1 | divideby2; auto m4 = divideby2(22); cout << boolalpha << "Right Identity result : " << (m3.value == m4.value) << endl; // Associativity // Case 1: equivalent to bind(m1, divideby2);bind(m5, even) auto m5 = m1 | divideby2; auto m6 = m5 | even; // Case 2: equivalent to Bind(m1, divideby2;bind(..., even)); // Create function composition auto f = [](int i){ return divideby2(i) | even; }; // Verbosely convert f to function pointer auto pf2 = static_cast<monad<bool>(*)(int)>(f); auto m7 = m1 | pf2; cout << boolalpha << "Associativity result : " << (m6.value == m7.value) << endl; cin.get(); return 0; }
which generates output:
The funny thing about the associative law is that the intuitive use of the bind operator like in:
auto m2 = m1 | divideby2 | even;
is equivalent to:
auto m2 = bind(bind(m1, divideby2), even);
which is very obviously equivalent to Case 1 above. So indeed, parentheses do not matter as long as we pipe the result of applying divideby2 to m1 into event.
Pros, Cons, and Questions
So, now that we have made a start, what to think of it?
The ‘pros’ are:
- The small size
- The very elegant function composition.
- From a functional programming perspective one might add that this implementation works with immutable data.
The most visible ‘cons’ are:
- The code copies function arguments and function results, so it is inefficient, and performance for large objects will be bad.
- The const qualifier is completely absent from the code.
- It would be more efficient to make the unit function the constructor in the monadic template (as it is in the examples in Eric Lippert’s tutorial on monads in C#).
There is also this question about the bind operator:
- Why is the bind operator not a member of the monadic template like with Lippert? Note that doing so would turn the monadic template into the complete monad. Well, this is C++, and I like to follow C++ best practices as accumulated by Sutter and Alexandrescu here, see item 44 “Prefer writing nonmember nonfriend functions”. There are more reasons, having to do with composition of different types of monads. But that’s for later…
Next time: a simple implementation of the monad for lazy evaluation.
[…] Harder to C++: Monads for Mortals [2], First implementation […]
[…] Harder to C++: Monads for Mortals [2], First implementation […]
[…] Harder to C++: Monads for Mortals [2], First implementation […]
nice one =) a few fixes to make it work (on g++ 4.8.x)
#include // ;
template struct monad // template; struct monad
template
monad operator| (const monad& mnd, monad(*func)(A)) // without “const”, i got compiling error “no known conversion for operator 1”
Thanks for your tips Catena!