“That’s actually the first time I read an explanation about monads that I understand.” – Commenter
“I just read your explanation, and suddenly I think I understand monads.” – Reviewer
Monads are notorious in the programming world for their use in the Haskell programming language and for being hard to grasp. There’s even a joke that writing a “monad tutorial” is a rite of passage for new Haskellers, and been described as pointless.1 I’ve been using Haskell for over a decade and have refrained from writing yet another monad tutorial.
A friend asked for an easy explanation of them that doesn’t involve Haskell code.2 That’s easier.
You can re-use your intuition from existing common place chaining of things in other popular languages:
One of the Haskell designers3 in the 90s just came up with a class/interface that worked for all of these. As he was into category theory, he related it to the idea of a “monad”. The types also sort of match the theory if you squint hard enough.
Then they came up with a syntax (actually, two4) that was syntactic sugar for calling the class methods to make it feel more ergonomic.
Parsers, CPS, async, optional chaining, all look more or less like this in most modern popular programming languages:
.and_then(x => putThingElsewhere(x+4)).and_then(..)getThing
For a parser you track the position in the string and plumb that through. For an “optional” it just short circuits if the left hand side of
and_then is nil/null/undefined. For async it would do something async, like make a web request to get the thing, and then call the callback, etc.
Monad is the name of the class for “and_then”,5 defined in a sensible way, with some laws for how it should behave predictably, and then a bunch of library code works on anything that implements “and_then”.
Apart from F# or Haskell (or descendants), no other language6 embraces the abstraction with syntax so it’s hard to find a good explanation without them. It’s like explaining Lisp macros without using a Lisp, the explanation tends to be awkward and unconvincing.
If you have mutation, you might not bother plumbing stuff for a parser, you might just update the position in place, and throw an exception on a parse error. If your language supports returning early from functions then you can just use that to short circuit (Rust does this). If your language puts in a special case for async with “await” syntax (JS, Rust, C#), you’ll just use that. Other ways of doing things just make the idea of a monad abstraction kind of unnecessary.
Haskellers don’t like to throw exceptions, or use mutation, and functions can’t return early, etc. Suddenly Monad and syntactic sugar for it looks pretty attractive to them.
Either do-notation or list comprehensions (yes, like in Python), which can be generalised to monad comprehensions. You can look that up if interested.↩︎
There happen to be a plethora of other things that implement the Monad class–some really weird stuff–but linking to them would just give you a big list of Haskell type signatures, which you need to know Haskell to understand. It’s not called “and_then”, either but that’s an implementation detail.↩︎
Well, OCaml and other ML-descendents do dabble with this these days.↩︎