Demystifying monads

# fp # monads

Googling “what a monad is” may yield this answer on StackOverflow, which explains, in exquisitely painful mathematical jargon, what a monad in fact is. But, no worries! Understanding monads doesn’t actually require you to learn the nuances of category theory or F-algebra. I rather suggest that the concept of monads can be distilled into a clearer single sentence:

A monad is a control abstraction that defines the composition of effectful functions.

Sounds way better than the notorious “A monad is just a monoid in the category of endofunctors”, if you ask me!

Now, let’s unpack.

Control abstractions

Control logic is the scaffolding around your actual program logic, things like:

  • Error propagation
  • Iteration
  • Stack manipulation
  • Virtual dispatch

Here’s a simple example in JavaScript:

function main() {
const { isError } = fallible(state)
if (isError) {
throw 'Error happened!'
}
return process(state)
}

That if (isError) check is control logic. A control abstraction provides a way to separate a control structure from the interesting code inside it. Common control abstractions are:

  • Exception handling, for error propagation
  • Objects and interfaces, for virtual dispatch
  • Function invocation, for stack manipulation
  • Memory management

The presence of control abstractions is one of, if not the, defining features of high-level languages. Rust, for example, provides destructors, automatic resource management, and smart pointers. Using these control abstractions simplifies control logic and results in less boilerplate needed to execute the code we’re actually interested in writing.

Effectful functions

An effect refers to any phenomenon that arises during program execution beyond just computing a value. Effects introduce additional behavior that affects the program’s control flow, state, or interaction with the external world, for instance:

  • Returning an error
  • Throwing an exception
  • Using IO or nondeterministic external state
  • Allocating or deallocating resources
  • Spawning child processes
  • Synchronizing in a multithreaded environment
  • Reading or modifying global variables

An effectful function, therefore, is a function that produces or consumes at least one effect. In most languages, any function can be an effectful function, but some languages make effects explicit, e.g. Java has checked exceptions; experimental languages like Koka or Eff have algebraic effects; OCaml recently introduced algebraic effects support as well.

Function composition

Function composition is a way to sequence functions by feeding the output of one function into the input of another one:

(f ∘ g)(x) = f(g(x))

This works fine for pure functions. But with effects involved, types often no longer line up — especially if g returns an effect and f doesn’t know how to deal with it.

That’s where monads come in.

Monads

A monad defines how to chain effectful functions. It provides two operations, usually called unit and bind. In Haskell-like syntax:

unit :: a -> m a
bind :: (a -> m b) -> (m a -> m b)

For those unfamiliar with Haskell syntax, the type t1 -> t2 is a function type, where t1 is an argument and t2 is a return type. And, the construction m t1 refers to the type t1 combined with the effect m.

unit wraps a value in the effect. bind chains effectful functions, handling the effects automatically.

But what is bind, exactly?

Monadic bind has several well-known implementations for specific effects. For lists (yes, list is a monad), streams, and iterators, bind is better known as flatMap; and for optionals and promises/futures it is better known as andThen or then. Often, bind implementations in object oriented languages will be defined as instance methods of the class that represents the associated effect.

The bind function can be seen in two complementary ways:

  • it “unwraps” a value out of the effect and runs that value through a function;
  • it transforms a value inside the effect, then flattens the nested effect.

The advantage of the second explanation is that it can be used for monadic effects which resist being modeled as simple data types (like the State monad instance).

An important thing to note is that by allowing the composition of effectful functions, bind allows the result of an effectful function to depend directly on the result of another effectful function. Haskell’s do notation, the async-await syntax of Rust, JavaScript/TypeScript, and C#, are all examples of bind being used to chain effectful expressions one after another.

Wrapping up

So, now — hopefully — you know what monads are for. Each monad instance defines how to route the control logic for a certain effect in such a way that functions producing that effect can be composed.

But, monads only deal with one effect at a time. How does one compose functions that have more than one effect? How does one compose effects?

Consider an iterator. An iterator follows the rules of the list monad, because you can (in principle) collect the elements of an iterator into a list. But what if your iterator doesn’t yield values directly, but instead yields a value in an effect?

In most programming languages, there is no way to use regular iteration to perform some effect. Instead, there are separate constructs in libraries for fallible iterators and asynchronous streams. As more and more effects are added into languages as first-class features, the problem only gets worse.

Fortunately, there is a way to unify these disparate interfaces and define a way to compose one effect with another… Monad transformers!