24

I am currently learning a little bit haskell and started to figure out how monads work. Since I normaly code C++ and I think the monad pattern would be (as fas as I understand it right now) be realy awesome to use in C++, too, for example for futures etc,

I wonder if there is a way to implement an interface, or a base class, to enforce a correct overload of the functions bind and return (of cause with another name than return for C++) for derived types?

To make more clear what I am thinking of:

consider we have the following non member function:

auto foo(const int x) const -> std::string;

And a member function bar which has different overloads in for different classes:

auto bar() const -> const *Monad<int>;

If we now want to do something like this: foo(someMember.bar()), this simply doesnt work. So if have to know what bar returns, and for example if it returns a future<int>, we have to call bar().get(), which blocks, even if we dont need to block here.

In haskell we could do something like bar >>= foo

So I am asking myself if we could achieve such a behaviour in C++, because when calling foo(x) we dont care if x is a object which boxes an int, and in what kind of class the int is boxed, we just want to apply function foo on the boxed type.

I am sorry I have some problem formulating my thoughts in english, since I am not a native speaker.

Exagon
  • 4,798
  • 6
  • 25
  • 53
  • 1
    It's a bit unclear what you mean. It might help to explain what problem this solves and maybe a hypothetical example. – Rakete1111 Sep 27 '16 at 12:55
  • 1
    You may want to look at `ranges/v3`: https://github.com/ericniebler/range-v3 I'm no expert but I think it may be related. – Galik Sep 27 '16 at 13:02
  • 1
    I'm not sure if it's of any use to you, but Bartosz Milewski has a series of articles on monads in C++ starting [here](https://bartoszmilewski.com/2015/05/11/using-monads-in-c-to-solve-constraints-1-the-list-monad/). – molbdnilo Sep 27 '16 at 13:08
  • 1
    This question is not really related to Haskell, because a monad is a generic programming concept. You should remove the haskell tag. – V. Semeria Sep 27 '16 at 13:08
  • Isn't a monad simply a lambda? – Richard Hodges Sep 27 '16 at 13:11
  • 2
    Yes, it is possible to do that with custom type traits. You can see an example in https://github.com/Corristo/functionalCpp The monad.h file has the type traits and automatically derives liftM and ap functions for monads. list.h implements the monadic bind for std::list – Corristo Sep 27 '16 at 13:12
  • @Corristo You should turn that into an answer, if you have time. It looks interesting. – chi Sep 27 '16 at 13:22
  • @V.Semeria removed the tag and added "functional-programming" – Exagon Sep 27 '16 at 13:24
  • @molbdnilo I allready know some of his stuff, I will read a little more of his articles, thank you – Exagon Sep 27 '16 at 13:24
  • @RichardHodges [here](http://stackoverflow.com/questions/44965/what-is-a-monad?rq=1) is a explanation of what a monad can achieve, I think its better to understand, how a monad works and why haskell needs it than to try understanding what a monad in general is – Exagon Sep 27 '16 at 13:27
  • @Corristo thanks for the link, I will have a look – Exagon Sep 27 '16 at 13:30
  • 1
    @galik The range-v3 container algorithms fulfill the same role of composable pipelines that monads seem to fulfill. Eric talks about them a bit more on his blog, and that would be a better place to evaluate their suitability: http://ericniebler.com/2014/11/23/container-algorithms/ – KitsuneYMG Sep 27 '16 at 13:40
  • @Exagon Thanks for the link. What I think it's saying is that the monad is the ultimate in logic obfuscation. I've heard many people (usually maths purists) wax lyrical about Haskell, but I've yet to see a program written in it that's understandable by anyone but a Haskell enthusiast :) – Richard Hodges Sep 27 '16 at 14:03
  • 3
    @RichardHodges Boost::future is monadic, and the Concurrency TS also introduces monadic futures to C++. The problem that monadic code in C++ currently is not very readable is more a problem of the language support than the complexity of monadic code itself. – Corristo Sep 27 '16 at 14:09
  • 1
    @RichardHodges understand this point of view, but since c++11/14 introduced futurese and c++17 will introduce ´std::optional´, it would be a charm if we could easily apply on all these a common monad pattern. Especially for futures, because using the monad pattern with futures could imho increase simplicity in writing concurrent code or libarys. If you want to learn a little bit more abaout what I mean with this, have a look at [this](https://www.youtube.com/watch?v=BFnhhPehpKw). – Exagon Sep 27 '16 at 14:12
  • @Exagon sure, I use function objects, lambdas and futures all the time. But I don't think about them as mathematical constructs. Simply objects that will eventually deliver a service or result. We've been doing this for years (almost decades). – Richard Hodges Sep 27 '16 at 14:21
  • 2
    @RichardHodges: simply objects that will eventually deliver a result – that's much the same Haskellers think about everything. Only, they give these objects very specific strong types that guarantee that it actually gives a _sensible_ result. – leftaroundabout Sep 27 '16 at 14:25
  • @Corristo did monadic futures (is that the correct term?) make it into c++17 in the end? – Richard Hodges Sep 27 '16 at 15:27
  • 1
    @RichardHodges No, they are only in the Concurrency TS and expected to make it into the next release after 17. There is a great talk about them by Bartosz Milewski called "I see a monad in your future" at https://www.youtube.com/watch?v=BFnhhPehpKw – Corristo Sep 27 '16 at 15:39
  • 1
    @Corristo that's a damn shame. Actually almost unforgivable. It's straightforward to implement, and there are working implementations in boost and asio. :/ – Richard Hodges Sep 27 '16 at 15:42
  • 2
    @chi So it is a quite long answer now :) – Corristo Sep 27 '16 at 17:03

3 Answers3

37

First note that being a monad is not a property of a type, but of a type constructor.

E.g. in Haskell you'd have List a as a type and List as the type constructor. In C++ we have the same functionality with templates: std::list is a type constructor that can construct the type std::list<int>. Here List is a monad, but List Bool is not.

In order for a type constructor M to be monadic it needs to supply two special functions:

  1. A function that lifts arbitrary values of some type T to the monad, i.e. a function of type T -> M<T>. This function is called return in Haskell.
  2. A function (in Haskell called bind) of the type M<T> ->(T -> M<T'>) -> M<T'>, i.e. a function that takes an object of type M<T> and a function of type T -> M<T'> and applies the argument function to the T objects wrapped inside the argument M<T>.

There are also some properties that these two functions have to fulfill, but since semantic properties cannot be checked at compile time (neither in Haskell nor in C++), we don't really need to care about them here.

What we can check however is the existence and types of these two functions once we decided on a syntax/names for them. For the first one, the obvious choice is a constructor that takes exactly one element of any given type T. For the second one I decided to go with operator>>= since I wanted it to be an operator in order to avoid nested function calls and it is similar to the Haskell notation (but unfortunately it is right-associative - oh well).

Checking the monadic interface

So how does one check properties of a template? Luckily there are template-template arguments and SFINAE in C++.

First, we need a way to figure out if there actually is a constructor that takes an arbitrary type. We can approximate that by checking that for a given type constructor M the type M<DummyType> is well formed for a dummy type struct DummyType{}; we define. This way we can make sure that there cannot be a specialization for the type we're checking against.

For bind we do the same thing: Check that there is an operator>>=(M<DummyType> const&, M<DummyType2>(*)(DummyType)) and that the returned type is actually M<DummyType2>.

Checking that a function exists can be done using C++17s std::void_t (I highly recommend Walter Browns talk at CppCon 2014 where he introduces the technique). Checking that the types are correct can be done with std::is_same.

All together this can look something like this:

// declare the two dummy types we need for detecting constructor and bind
struct DummyType{};
struct DummyType2{};

// returns the return type of the constructor call with a single 
// object of type T if such a constructor exists and nothing 
// otherwise. Here `Monad` is a fixed type constructor.
template <template<typename, typename...> class Monad, typename T>
using constructor_return_t
    = decltype(Monad<T>{std::declval<T>()});

// returns the return type of operator>>=(const Monad<T>&, Monad<T'>(*)(T))
// if such an operator is defined and nothing otherwise. Here Monad 
// is a fixed type constructor and T and funcType are arbitrary types.
template <template <typename, typename...> class Monad, typename T, typename T'>
using monadic_bind_t
    = decltype(std::declval<Monad<T> const&>() >>= std::declval<Monad<T'>(*)(T)>());

// logical 'and' for std::true_type and it's children
template <typename, typename, typename = void>
struct type_and : std::false_type{};
template<typename T, typename T2>
struct type_and<T, T2, std::enable_if_t<std::is_base_of<std::true_type, T>::value && std::is_base_of<std::true_type, T2>::value>> 
    : std::true_type{};


// the actual check that our type constructor indeed satisfies our concept
template <template <typename, typename...> class, typename = void>
struct is_monad : std::false_type {};

template <template <typename, typename...> class Monad>
struct is_monad<Monad, 
                void_t<constructor_return_t<Monad, DummyType>,
                       monadic_bind_t<Monad, DummyType, DummyType2>>>
    : type_and<std::is_same<monadic_bind_t<Monad, DummyType, DummyType2>,
                            Monad<DummyType2>>,
               std::is_same<constructor_return_t<Monad, DummyType>,
                            Monad<DummyType>>> {};

Note that even though we generally expect the type constructor to take a single type T as an argument, I've used a variadic template template parameter to account for default allocators typically used in STL containers. Without that you couldn't make std::vector a monad in the sense of the concept defined above.

Using the type trait to implement generic functions based on the monadic interface

The great advantage of monads is that there are quite a lot of things one can do with only the monadic interface. For example we know that every monad is also an applicative, so we can write Haskell's ap function and use it to implement liftM that allows to apply any ordinary function to a monadic value.

// ap
template <template <typename, typename...> class Monad, typename T, typename funcType>
auto ap(const Monad<funcType>& wrappedFn, const Monad<T>& x) {
    static_assert(is_monad<Monad>{}(), "");
    return wrappedFn >>= [x] (auto&& x1) { return x >>= [x1 = std::forward<decltype(x1)>(x1)] (auto&& x2) {
        return Monad<decltype(std::declval<funcType>()(std::declval<T>()))> { x1 (std::forward<decltype(x2)>(x2)) }; }; };
}

// convenience function to lift arbitrary values into the monad, i.e.
// just a wrapper for the constructor that takes a single argument.
template <template <typename, typename...> class Monad, typename T>
Monad<std::remove_const_t<std::remove_reference_t<T>>> pure(T&& val) {
    static_assert(is_monad<Monad>{}(), "");
    return Monad<std::remove_const_t<std::remove_reference_t<T>>> { std::forward<decltype(val)>(val) };
}

// liftM
template <template <typename, typename...> class Monad, typename funcType>
auto liftM(funcType&& f) {
    static_assert(is_monad<Monad>{}(), "");
    return [_f = std::forward<decltype(f)>(f)] (auto x) {
        return ap(pure<Monad>(_f), x);
    };
}

// fmap
template <template <typename, typename...> class Monad, typename T, typename funcType>
auto fmap(funcType&& f, Monad<T> const& x) {
    static_assert(is_monad<Monad>{}(), "");
    return x >>= ( [_f = std::forward<funcType>(f)] (const T& val) {
        return Monad<decltype(_f(std::declval<T>()))> {_f(val)}; });
}

Let's see how we can use it, assuming that you already implemented operator>>= for std::vector and optional.

// functor similar to std::plus<>, etc.
template <typename T = void>
struct square {
    auto operator()(T&& x) {
        return x * std::forward<decltype(x)>(x);
    }   
};

template <>
struct square<void> {
    template <typename T>
    auto operator()(T&& x) const {
        return x * std::forward<decltype(x)>(x);
    }
};

int main(int, char**) {
    auto vector_empty = std::vector<double>{};
    auto vector_with_values = std::vector<int>{2, 3, 31};
    auto optional_with_value = optional<double>{42.0};
    auto optional_empty = optional<int>{};

    auto v1 = liftM<std::vector>(square<>{})(vector_empty); // still an empty vector
    auto v2 = liftM<std::vector>(square<>{})(vector_with_values); // == vector<int>{4, 9, 961};
    auto o1 = liftM<optional>(square<>{})(optional_empty); // still an empty optional
    auto o2 = liftM<optional>(square<>{})(optional_with_value); // == optional<int>{1764.0};

    std::cout << std::boolalpha << is_monad<std::vector>::value << std::endl; // prints true
    std::cout << std::boolalpha << is_monad<std::list>::value << std::endl; // prints false

}

Limitations

While this allows for a generic way to define the concept of a monad and makes for straightforward implementations of monadic type constructors, there are some drawbacks.

First and foremost, I'm not aware that there is a way to have the compiler deduce which type constructor was used to create a templated type, i.e. there is no way that I know of to have to compiler figure out that the std::vector template has been used to create the type std::vector<int>. Therefore you have to manually add the name of the type constructor in the call to an implementation of e.g. fmap.

Secondly, it is quite ugly to write functions that work on generic monads, as you can see with ap and liftM. On the other hand, these have to be written only once. On top of that the whole approach will become alot easier to write and to use once we get concepts (hopefully in C++2x).

Last but not least, in the form I've written it down here, most of the advantages of Haskell's monads are not usable, since they rely heavily on currying. E.g. in this implementation you can only map functions over monads that take exactly one argument. On my github you can find a version that also has currying support, but the syntax is even worse.

And for the interested, here is a coliru.

EDIT: I just noticed that I was wrong regarding the fact that the compiler can not deduce Monad = std::vector and T = int when supplied an argument of type std::vector<int>. This means that you really can have a unified syntax for mapping a function over an arbitrary container with fmap, i.e.

auto v3 = fmap(square<>{}, v2);
auto o3 = fmap(square<>{}, o2);

compiles and does the right thing.

I added the example to the coliru.

EDIT: Using concepts

Since C++20's concepts are right around the corner, and the syntax is pretty much final, it makes sense to update this reply with equivalent code that uses concepts.

The simplest thing you can do to make this work with concepts is to write a concept that wraps the is_monad type trait.

template<template<typename, typename...> typename T>
concept monad = is_monad<T>::value;

Though, it could also be written as a concept by itself, which makes it a bit clearer.

template<template<typename, typename...> typename Monad>
concept monad = requires {
    std::is_same_v<monadic_bind_t<Monad, DummyType, DummyType2>, Monad<DummyType2>>;
    std::is_same_v<constructor_return_t<Monad, DummyType>, Monad<DummyType>>;
};

Another neat thing this allows us to do is cleaning up the signature of the generic monad functions above, like so:

// fmap
template <monad Monad, typename T, typename funcType>
auto fmap(funcType&& f, Monad<T> const& x) {
    return x >>= ( [_f = std::forward<funcType>(f)] (const T& val) {
        return Monad<decltype(_f(std::declval<T>()))> {_f(val)}; });
}
Corristo
  • 4,911
  • 1
  • 20
  • 36
  • Wow thanks for this one 0.0 I am not familiar with concepts, what would be much easiee if we would have concepts? – Exagon Sep 27 '16 at 17:23
  • 1
    The Concepts TS introduces `requires` clauses, so instead of having all that template stuff you coud just write something like `requires isMonad(T)` and the monad concept itself could be written as `concept isMonad requires && `. I've not looked into the correct syntax, but something along these lines. – Corristo Sep 27 '16 at 17:28
  • 1
    I just noticed that actually the compiler can deduce the correct components of an argument such as `std::vector`. I updated the answer and the coliru. I think this is now exactly what you asked for :D – Corristo Sep 27 '16 at 19:25
  • This is much more than i thought i would get as an answer. Thank you very much – Exagon Sep 27 '16 at 19:33
  • 3
    You're welcome. I actually started as a Haskell programmer and only recently switched to C++, so naturally I was interested in getting typeclasses to C++. This was the perfect question for me and I am excited that there are other people who appreciate these Haskell features just as much as I do :) – Corristo Sep 27 '16 at 19:42
  • @Corristo : Read through some of [@Yakk](https://stackoverflow.com/users/1774667/)'s answers from the last 6-12 months – he's embraced return type deduction and generic lambdas quite wholeheartedly to apply full-fledged FP in modern C++, it's very refreshing! – ildjarn Sep 27 '16 at 19:51
  • @ildjarn Thanks for the hint, I'll have a look. – Corristo Sep 27 '16 at 19:52
  • Instead of `>>=` you could use `->*bind*` ;) – Yakk - Adam Nevraumont Sep 27 '16 at 20:38
  • 1
    I could've, but as I've written I wanted it to be an operator so that you wouldn't need to write `bind(bind(bind(val, f1), f2, f3)` but rather `val >>= f1 >>= f2 >>= f3`. Unfortunately `operator>>=` is right-associative, so you get to write `((val >>= f1) >>= f2) >>= f3` now... – Corristo Sep 27 '16 at 20:41
  • @Corristo No, not `bind(a,b)`, `a ->*bind* b`. [Named operators](https://stackoverflow.com/questions/15090209/does-boost-have-a-datatype-for-set-operations-that-is-simpler-than-the-stl/15090751#15090751). (but with `->*` as the lhs operator and `*` as the rhs operator around the named token). Also [`can_apply`](http://stackoverflow.com/a/30195655/1774667) might clean up some of your SFINAE testing. – Yakk - Adam Nevraumont Sep 28 '16 at 13:13
  • How about that simple Approach? #include #include template struct Monad { explicit Monad(A a) : m(a) {} A m; }; Monad Times2(const Monad& a) { return Monad(a.m * 2); }; int main() { Monad M1(1); auto M2 = Times2(M1); auto M3 = Times2(M2); assert(M3.m == M1.m * 4); return 0; } Did i fully missread this? https://en.wikipedia.org/wiki/Monad_(functional_programming)#Overview – schorsch_76 Sep 29 '16 at 08:47
  • @Yakk Oh wow, mind = blown. I'll definitively use both in the future. – Corristo Sep 30 '16 at 10:34
  • 1
    @schorsch_76 Yeah, you misread that. Both in this comment and your answer below it seems you assumed that monad is just a fancy word for `optional`, whereas `optional` simply is one example of a monad. As you can see in my example above, even `std::vector` can be made into a monad by supplying `bind`. This allows for much more reusable code, since any function defined on `int`s can be applied to both `optional` and `std::vector` by calling `fmap(fn, arg)`, i.e. you don't need a special function for `optional` *and* one for `std::vector` but can use the same one for both. – Corristo Sep 30 '16 at 10:36
6

I fear that Haskell-style polymorphism and C++ templates are too far to pragmatically define monads in C++, in a way that it is actually usable.

Technically, you might define a monad M to be a template class of the following form (I'll be passing everything by value to keep it simple)

template <typename A>
struct M {
   // ...

   // this provides return :: a -> M a
   M(A a) { .... }

   // this provides (>>=) :: M a -> (a -> M b) -> M b
   template <typename B>
   M<B> bind(std::function< M<B> (A) > f) { ... }

   // this provides flip fmap :: M a -> (a -> b) -> M b
   template <typename B>
   M<B> map(std::function< B (A) > f) { ... }
};

This might work (I'm no C++ expert), but I'm unsure about whether it is usable in C++. Surely it would lead to non idiomatic code.

Then, your question is about how to require that a class has such interface. You could use something like

template <typename A>
struct M : public Monad<M, A> {
...
};

where

template <template <typename T> M, typename A>
class Monad {
   // this provides return :: a -> M a
   Monad(A a) = 0;

   // this provides (>>=) :: M a -> (a -> M b) -> M b
   template <typename B>
   M<B> bind(std::function< M<B> (A) > f) = 0;

   // this provides flip fmap :: M a -> (a -> b) -> M b
   template <typename B>
   M<B> map(std::function< B (A) > f) = 0;

};

But, alas,

monads.cpp:31:44: error: templates may not be ‘virtual’
   M<B> bind(std::function< M<B> (A) > f) = 0;

Templates look similar to polymorphic functions, but they are a different thing.


New approach, which seems to work, but it does not:

template <template <typename T> typename M, typename A>
class Monad {
  // this provides return :: a -> M a
  Monad(A a) = 0;

  // this provides (>>=) :: M a -> (a -> M b) -> M b
  template <typename B>
  M<B> bind(std::function< M<B> (A) > f);

  // this provides flip fmap :: M a -> (a -> b) -> M b
  template <typename B>
  M<B> map(std::function< B (A) > f);

};

// The identity monad, as a basic case
template <typename A>
struct M : public Monad<M, A> {
  A x;
  // ...

  // this provides return :: a -> M a
  M(A a) : x(a) { }

  // this provides (>>=) :: M a -> (a -> M b) -> M b
  template <typename B>
  M<B> bind(std::function< M<B> (A) > f) {
    return f(x);
  }

  // this provides flip fmap :: M a -> (a -> b) -> M b
  template <typename B>
  M<B> map(std::function< B (A) > f) {
      return M(f(x));
  }
};

However, removing, say map, from the M type does not trigger a type error. Indeed, errors will only be generated at instantiation time. Templates are not foralls, again.

chi
  • 111,837
  • 3
  • 133
  • 218
  • 7
    The trick is to use type traits and free functions instead of a templated base class. This has the further advantage that you don't actually need to derive from `Monad` in order to be a monad. E.g. std::vector, std::list etc can be used in monadic context as well. – Corristo Sep 27 '16 at 13:13
0

I think the most Basic form of this style of programming in c++ is something like this:

#include <functional>
#include <cassert>
#include <boost/optional.hpp>

template<typename A>
struct Monad
{
public:
    explicit Monad(boost::optional<A> a) : m(a) {}

    inline bool valid() const { return static_cast<bool>(m); }
    inline const A& data() const {  assert(valid());  return *m;  }
private:
    const boost::optional<A> m;
};

Monad<double> Div(const Monad<double>& ma, const Monad<double>& mb)
{
    if (!ma.valid() || !mb.valid() ||  mb.data() == 0.0)
    {
        return Monad<double>(boost::optional<double>{});
    }
    return Monad<double>(ma.data() / mb.data());

};
int main()
{
    Monad<double> M1(3);
    Monad<double> M2(2);
    Monad<double> M0(0);

    auto MR1 = Div(M1, M2);
    if (MR1.valid())
        std::cout << "3/2 = " << MR1.data() << '\n';

    auto MR2 = Div(M1, M0);
    if (MR2.valid())
        std::cout << "3/0 = " << MR2.data() << '\n';

    return 0;
}
schorsch_76
  • 794
  • 5
  • 19