Published July 14, 2017 - 3 Comments

Now that we’ve got our feet wet and have a feeling of the vector monad in C++, let’s use modern C++ to make a more elaborate implementation of the vector monad, but that leads to cleaner code.

You’ll note that the way of thinking here has a lot in common with the optional monad in C++, even though it was focused on multiple error handling while the vector monad aims at chaining functions returning multiple arguments.

For the sake of the example, let’s take three functions that take and return integers:

1 2 3 |
int f1(int a); int f2(int b, int c); int f3(int d); |

These functions, like all normal functions in the world of functions, take one version of their parameters.

But what if there were **several versions of their parameters**? Or, said differently, what if we had a vector of values for each argument, and wanted to get out of the function a vector of results, that would contain the results of the function applied to **every possible combination of arguments**?

*(Little legal note for the functional aficionados: Okay, this is not a monad, it’s an applicative (thanks Quentin). But the use case is interesting and we’ll get to the actual monad in section 2 anyway!)*

The way I want to show you is to encapsulate the mechanism of applying a function to **all possible combinations** of arguments. If you’re a regular reader of Fluent C++, doesn’t this sound familiar to you? Of course! The Cartesian product range adaptor!!

Indeed, `cartesian_product`

, now available in the range v3 library, is exactly made for that job: applying a function to all the possible combinations of elements in several ranges.

Let’s use it to encapsulate the mechanism:

1 2 3 4 5 6 7 8 9 10 |
template <typename Res, typename ... Args> auto make_multiple(Res (*f)(Args...)) { return [f](std::vector<Args> const& ... args) -> std::vector<Res> { std::vector<Res> results; ranges::push_back(results, ranges::view::cartesian_product(args...) | ranges::view::transform(tupled_args(f))); return results; }; } |

Don’t panic, here is how to read this code:

`make_multiple`

is a function that takes a function `f`

, and returns another function (well, a lambda to be precise). By the way this particular implementation only supports functions, and not callable objects in general (and thank you Vittorio for your suggestions on that topic).

The lambda that it returns takes `vector<Args>`

s as arguments where `f`

only took `Args`

s, and it returns a `vector<Res>`

where `f`

only returned one `Res`

.

`ranges::view::cartesian_product(xs...)`

this cartesian product is a range view over all the possible combinations of the elements in the variadic pack of vectors `xs...`

. These combinations are piped through to a `range::view::transform`

to apply the function on each on of them. If you’re not into ranges yet, it’s a very popular library you really want to get familiar with, because it is likely the future of the STL.

Finally there is this `tuple_args`

thing. If you’re not curious about it it’s fine, move on to the next paragraph, you won’t need it. But if you *are* curious about it, it is necessary because `cartesian_product`

produces a view over a range of which each element represents a combination of values. So each of these elements is a tuple. But `f`

can’t be applied directly on a tuple, so `tupled_args`

forwards the elements of the tuples as arguments of `f`

. If you’re still curious, you can unveil the following code to see my implementation of `tupled_args`

:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
template<typename Function, typename Args, size_t... index> auto tupled_args_impl(Function func, Args const& args, std::index_sequence<index...>) { return func(std::get<index>(args)...); } template<typename Res, typename... Args> auto tupled_args(Res(*func)(Args...)) { return [func](std::tuple<Args...> const& args) { return tupled_args_impl(func, args, std::make_index_sequence<sizeof...(Args)>{}); }; } |

And here is how `make_multiple`

can be used:

1 2 3 4 |
auto multiple_f1 = make_multiple(f1); auto multiple_f2 = make_multiple(f2); std::vector<int> results = multiple_f3(multiple_f2(multiple_f1({1, 2, 3}), multiple_f1({3, 4, 5}))); |

Vectors in, vectors out, and what’s best: **the implementation of f is unchanged**.

Until now we’ve dealt with passing along multiple parameters to generate multiple return values. Now how about generating multiple values directly in our functions taking a single set of parameters? Multiplicity has to start somewhere!

Let’s modify `f2`

so that it takes one version of each of its two parameters, and returns a vector of resulting values:

1 2 3 |
int f1(int a); std::vector<int> f2(int b, int c); int f3(int d); |

Can you think of a way to adapt the `make_multiple`

function so that it still works with the new version of `f2`

? More precisely, how to keep chaining up calls to `multiple_f1`

, `multiple_f2`

and `multiple_f3`

so that they can still pass along vectors of values, but for each of the values coming out of `multiple_f1`

, `multiple_f2`

would generate several results? So `multiple_f2`

would produce a big big vector in a way.

Before reading further, take a moment to think about how you would go about implementing this.

Maybe re-read the instructions, and even the article from the beginning, and even the full story starting in the previous post if you feel it’s necessary. It takes time to get accustomed to this way of programming (at least it did for me!).

Done?

Okay, so here is one way to go about it: each application of `f`

on a combination of arguments returns a vector, so to put all the results into one single vector we need to **concatenate** all the results.

EDIT: Quentin Duval made many suggestions to improve this implementation, for which I’m very grateful. I haven’t processed them all yet but one of them is that the range v3 implements the desired concatenation with the `join`

view adaptor, which we’ll use here:

1 2 3 4 5 6 7 8 9 10 |
template <typename Res, typename ... Args> auto make_multiple(std::vector<Res> (*f)(Args...)) { return [f](std::vector<Args> const& ... args) -> std::vector<Res> { std::vector<std::vector<Res>> functionResults; ranges::push_back(functionResults, ranges::view::cartesian_product(args...) | ranges::view::transform(tupled_args(f))); return functionResults | ranges::view::join; }; } |

With this we can write the chain of functions that pass on and creates multiple arguments and return values:

1 2 3 4 5 |
auto multiple_f1 = make_multiple(f1); auto multiple_f2 = make_multiple(f2); auto multiple_f3 = make_multiple(f3); std::vector<int> results = multiple_f3(multiple_f2(multiple_f1({1, 2, 3}), multiple_f1({3, 4, 5}))); |

Okay, we’re at the end of a 4 posts-series on functional programming concepts applied to C++. Two were on optional and multiple error handling, and the other two were on vector and dealing with multiple values.

This way of programming is unusual in C++. But I believe that borrowing the concepts of functional programming can nudge us towards the terse and expressive writing these languages tend to have. Now how exactly to implement these concepts in C++ is still open to the question. I’ve shown some implementations (special thanks to Jacek), but there are surely better ones out there, or waiting to be written.

Now what do YOU think? To which extent do you use functional programming principles in your C++ code? Do you use monads? Do you use others than optional and vector? Share your experience with us, and make everyone benefit from it!

Share this post! Don't want to miss out ?