The Vector Monad in C++, Really Without the Ugly Stuff
Jonathan Boccara has been writing a series about introducing monadic concepts to C++ lately, the last of which has been The Vector Monad in C++, Without the Ugly Stuff. The example is ultimately about taking three functions:
And trying to get the result from a cartesian product of calling
f1 with 1, 2, and 3 and then again with 3, 4, 5, passing those results into
f2, and those results again into
f3. The end result is:
For the implementation of
make_multiple, see the post itself. I have two problems with this code:
- It’s not expressive. It’s very difficult to figure out what exactly
resultsholds and how we got there.
- You have to wrap every function you use. In a functional programming model, that’s a little backwards, since most of the things we use are functions.
Instead, I would like to present an alternative way to use the vector monad in C++ without the ugly stuff. There are two member functions we wish we had on
std::vector but actually don’t:
and_then(). The latter is more commonly known as
bind(), but I really dislike that name. I find
and_then() expresses the flow of code better and is less confusing with
std::bind(). Without regard for performance (we’re just worried about functionality and expressivity today), those member functions are pretty easy to implement:
Now obviously, we can’t just go adding member functions to
std::vector and I’ll get into a proper way to do this later, but let’s start simple. What would this give us? It let’s us use rewrite the initial example like so:
This, for me, is much easier to understand. We’re unpacking one vector into
a and the other into
b and then we just call the functions we have directly. Note that
f3 are used untouched. This is still a lot more typing than a C++ equivalent of do-notation would be (which is itself the fundamental problem of trying to stick monads into C++), but the flow is there.
So okay, that was basically just cheating. We can’t just add member functions like that. Can we do something without cheating? Sure. Instead of making
and_then() member functions (we didn’t use any of
vector's internals anyway), we can make them piped function objects. Here’s a very simplified example of
map() that’s specific to
vector (although hopefully it’s clear how this could be extended with customization points):
Put it together, and here’s the vector monad without the ugly stuff, my attempt:
Demo on Wandbox.
The astute reader (basically, T.C.) will notice that the
and_then() implementations are both under-constrained as they use
std::invoke_result_t but use normal function call notation. You’d think that having even written a language proposal expressly pointing out this common error would have led me to be less likely to make it (p0312r1). Apparently not. I’m leaving the bug in because I find it amusing.