Alex Vakhitov

software engineering4 min read

Monads 101: the common ones in plain terms

By Alex Vakhitov

Diagram of connected boxes labelled value, context and bind around a node marked MONADS 101

Monads come from category theory, but you don't need any category theory to use them. In practice a monad is two operations. return wraps a plain value in a context. bind (written >>= in Haskell) takes a wrapped value and a function and chains them, while the context handles the extra work: failure, several results, state or I/O. This post goes through the common ones in programming terms.

Identity monad

The Identity monad is the simplest of all. It wraps a value but adds no extra behaviour. When you bind a function to an Identity value, the function is applied directly to the wrapped value. In code, return takes a value and wraps it, and bind unwraps the value, applies the function and returns the result, which is already wrapped.

Maybe monad

The Maybe monad is for computations that can fail or return nothing (where other languages would use null). A Maybe value is either Just a value or Nothing. When you bind a function to a Maybe, the function is applied only if the value is a Just. Otherwise bind returns Nothing, which short-circuits the rest of the computation.

Either monad

Like Maybe, the Either monad is for computations that might fail, but it tells you more about the failure. An Either value is either a Left holding an error description or a Right holding a value. When you bind a function, it's applied only if the value is a Right.

List monad

The List monad is for computations that can return several results. When you bind a function to a list, it applies the function to each element and concatenates the resulting lists. For example, binding the list [1, 2, 3] to a function that turns a number x into the list [x, x+1] gives [1, 2, 2, 3, 3, 4].

State monad

The State monad threads a state value through a computation. Given an initial state, it returns a result and a new state. It wraps functions of type s -> (a, s), where s is the state and a is the result. It's useful for stateful computations such as random number generation.

IO monad

The IO monad is for computations with side effects, such as reading a file or making a network request. It wraps operations that interact with the outside world, so that the rest of a functional program can stay pure. An IO value describes a computation that, when run, performs I/O and produces a result. (I've written more about side effects and I/O separately.)

Continuation monad

The Continuation monad represents computations in continuation-passing style (CPS). In CPS, a function doesn't return a value; instead it takes an extra argument, a continuation function, and passes its result to that. The monad wraps functions of the form (a -> r) -> r, where a -> r is the continuation.

Reader monad

The Reader monad is for computations that need access to a shared environment, such as configuration. It wraps a function that takes the environment and returns a value. It's useful for things like dependency injection.

Writer monad

The Writer monad is for computations that produce an extra stream of output, such as log messages. It pairs the result with a log or other data, usually combined using a monoid.

Custom monads

Custom monads are built for domain-specific needs. Often they're combinations of the monads above, and monad transformers are the standard way to stack them: for example, adding Maybe-style failure or Reader-style configuration on top of IO. The key is to capture one kind of computational context and expose it through return and bind.

The monad laws

What makes chaining predictable is that every monad obeys three laws. In Haskell notation:

  • Left identity: return a >>= f is the same as f a.
  • Right identity: m >>= return is the same as m.
  • Associativity: (m >>= f) >>= g is the same as m >>= (\x -> f x >>= g).

In plain terms: wrapping a value and then binding it does nothing extra, and it doesn't matter how you group a chain of steps.

Key takeaways

  • Monads capture computational contexts such as state, failure or side effects.
  • Each monad has its own use case and behaviour.
  • You don't need to understand category theory to use them in programming.

Once you recognise the pattern, you'll see it in error handling, async code and parsers, even in languages that never use the word.

Monads also have a mirror image, the comonad, which turns the arrows around: instead of return putting a value into a context, extract takes one out, and extend plays the part of bind. I explain it, and why my company carries the name, in Why I named my company Comonad.