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:

  1. It’s not expressive. It’s very difficult to figure out what exactly results holds and how we got there.
  2. 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: map() and 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 f1, f2, and 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 map() and 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 map() and 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.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.