4

I'm currently trying to wrap my head around monads. Unfortunately, most articles on the topic use Haskell without properly explaining the notation. Yet, as I am mainly programming in C++, I would like understand monads without learning a new programming language...

From what I gathered on the web, a monad M is a type constructor for a type T, which provides at least the following operations:

  • an actual way to construct the type T
  • a converter for converting an arbitrary type to T (apparently called return in Haskell)
  • a combinator for applying the value stored in T to a function f (apparently called bind in Haskell)

Applying these criteria to C++, it seems to me that std::unique_ptr could be considered a monad. Is this true?


My reasoning is as follows:

The std::unique_ptr template is used to construct the actual type std::unique_ptr<T>, thus:

  • the type constructor is either std::unique_ptr<T>{} or std::make_unique<T>()
  • the converter, again, would be the constructor or std::make_unique (with arguments...)
  • the combinator would be either std::bind(func, pointer.get()), std::bind(func, *pointer) or an equivalent lambda

Do you agree, or does the call to operator*()/.get() for the combinator disqualify std::unique_ptr from being a monad?


I get, that using std::unique_ptr as a monad might not make sense because it carries owner semantic. I would just like to know, if it is one.

jan.sende
  • 750
  • 6
  • 23
  • Some noodling here: https://thebytekitchen.com/2014/12/05/harder-to-c-monads-for-mortals-5-pointers/ – Robert Harvey Jul 22 '19 at 17:26
  • and here: https://www.modernescpp.com/index.php/monads-in-c. Apparently not. – Robert Harvey Jul 22 '19 at 17:28
  • 1
    Here's a question: what does it matter? C++ as a language and library has basically none of the supporting infrastructure to make monads a thing. That is, two C++ things which happen to fit the definition of a "monad" may not have any interfaces in common, nor will there be a simple way to write a function that could take either of them (or any "monadic" type). So it's not clear what it matters if a particular type just so happens to fit the definition of a word. It won't change what the type does or what you can do with it, because the support infrastructure for that word doesn't exist. – Nicol Bolas Jul 22 '19 at 17:29
  • and here: https://bartoszmilewski.com/2014/02/26/c17-i-see-a-monad-in-your-future/ Still no mention, although `std::unique_ptr` is mentioned in the context of a functor. – Robert Harvey Jul 22 '19 at 17:30
  • This might be helpful: https://stackoverflow.com/questions/44965/what-is-a-monad – Eljay Jul 22 '19 at 17:36
  • @RobertHarvey Thanks for the links! I'm currently skimming through them... Very interesting! – jan.sende Jul 22 '19 at 17:47
  • @Eljay As I mentioned... I don't understand Haskell! I saw this article before, and it is mostly gibberish to me. The notation is just too weird... – jan.sende Jul 22 '19 at 17:48
  • @NicolBolas Sure, but there seem to be lots of people liking monads for some reason, and I would like to understand why, plus how they work... Maybe if not monads themselves, some parts of them might be useful when writing c++... – jan.sende Jul 22 '19 at 17:51
  • "I would just like to know, if [ `std::unique_ptr` ] is one [ a monad ]." No, `std:unique_ptr` is not a monad. Your definition of monad is egregiously incorrect. – Eljay Jul 23 '19 at 12:25
  • @Eljay It's fairly simple to supply `auto unit(auto v){ return std::make_unique(v); }` and `auto bind(auto ptr, auto f) { return ptr ? f(*ptr) : nullptr; }` and see that it's `Maybe` / `Option` – Caleth Jul 23 '19 at 13:24

2 Answers2

4

Applying these criteria to C++, it seems to me that std::unique_ptr could be considered a monad. Is this true?

Your definition is missing the monad laws, but we can see that there is appropriate formulation by which std::unique_ptr (plus it's bind and return/unit) obeys them.

Given

template <typename T>
std::unique_ptr<T> unit(T t) { return std::make_unique<T>(t); }

template <typename T, typename U, typename F = std::function<std::unique_ptr<U>(T)>>
std::unique_ptr<U> bind(std::unique_ptr<T> ptr, F f) { return ptr ? f(*ptr) : nullptr; }

and a notion of expression equivalence (), i.e. "these two expressions result in the same value"

We require

  • Left identity: bind(unit(a), f) ≡ f(a)
  • Right identity: bind(m, unit) ≡ m
  • Associativity: bind(bind(m, f), g) ≡ bind(m, [](auto x){ return bind(f(x), g); })

I get, that using std::unique_ptr as a monad might not make sense because it carries owner semantic. I would just like to know, if it is one.

A monad is something that applies a semantic, like unique_ptr's ownership, or vectors multiplicity, or future's asynchronicity. There are lots of things in C++ that are monads, but (as @NicolBolas notes) there isn't much that operates on the monadic structure.

Caleth
  • 52,200
  • 2
  • 44
  • 75
3

I don't think the std::bind is the same thing as a monadic bind, despite having the same name.

Your reasoning is on the right path though. return is trivial for most types. bind is a little trickier. Say you have a function f that that takes an A and returns a std::unique_ptr<B>. The bind would need to take a pointer to f and a std::unique_ptr<A> as arguments, and return a std::unique_ptr<B>. This function may not be already in the standard library, but it wouldn't be too difficult to write. It basically "unwraps" the std:unique_ptr<A> into an A, then calls f on it.

People get hung up on wanting one abstract bind that will work for any monad, like in Haskell's typeclass. That undoubtedly makes it easier to use monads, but it isn't a requirement to identify one conceptually.

I personally think the array monad is the easiest to grasp, because people are so familiar with it they may not even realize it's a monad. Say you have a function f that takes a string and returns an array of the characters in the string. Calling bind(f, ["two", "strings"]) would return ['t', 'w', 'o', 's', 't', 'r', 'i', 'n', 'g', 's']. If you have a g that takes an integer n and returns an array from 1 to n, then you can use the exact same bind function to do bind(g, [2, 0, 3]) with a result of [1, 2, 1, 2, 3].

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • Thanks, this is enlightening! I somewhere read about **bind** acting on a `std::function(T)>`, but forgot about it... Do you know if there is a specific reason why `f` has to have this signature? Or could it also be `std::function`, with **bind** wrapping the result into a `std::unique_ptr`? Plus, I like the array example. Would you mind expanding on it, for the benefit of other readers? – jan.sende Jul 23 '19 at 09:02
  • `f` having that signature is just how monads are defined. There's no reason you can't additionally support other signatures for `f` as you described, but without the one I described, it doesn't technically meet the definition. – Karl Bielefeldt Jul 23 '19 at 12:44
  • Looking at @Caleths implementation of **bind**, the signature of `std::function(T)>` makes sense now. `return ptr ? f(*ptr) : nullptr;` would not make sense (and wouldn't compile) if we had `std::function`... – jan.sende Jul 23 '19 at 13:35
  • there is no one abstract bind that works for any monad in Haskell. Each Monad type defines its own; they are all just called "bind". It's just an overloaded function. Haskell's *typeclasses* were invented specifically so that Haskell could have function overloading, and Monad is a typeclass. – Will Ness Jul 23 '19 at 15:10
  • There is more than one way to create an abstraction. Overloading is one of them. – Karl Bielefeldt Jul 23 '19 at 15:25
  • I merely commented on the Haskell's side of things. typeclasses were invented to have an "ad-hoc polymorphism", a Haskell jargon for function / operator / overloading. it is actually very confusing when monads are presented as some magic that works for every instance, when in fact each instance *defines* how it works, by definition. OTOH *after* the pattern is internalized, there's advantages to thinking in abstract terms of course. – Will Ness Jul 23 '19 at 15:53