Monads

Suppose we have a task: a function takes an integer and divides it by 2. If the result is odd, the function returns no value. If the result is even, it divides it by 2 once more and then multiplies it by 10 if the new result is even, and by 5 if it is odd. Let's first write such a function in TypeScript.

function processNumber(value: number): number | undefined {
  const halved = Math.floor(value / 2);

  if (halved % 2 !== 0) {
    return undefined;
  }

  const halvedAgain = halved / 2;

  return halvedAgain % 2 === 0
    ? halvedAgain * 10
    : halvedAgain * 5;
}

Now let's rewrite this code in Haskell head-on. I'll say right away: this is not how things are done in Haskell.

processNumber :: Int -> Maybe Int
processNumber x =
  let halved = x `div` 2
  in case even halved of
    False -> Nothing
    True  ->
      let halvedAgain = halved `div` 2
      in case even halvedAgain of
        True  -> Just (halvedAgain * 10)
        False -> Just (halvedAgain * 5)

What clearly looks bad in the code above is the nesting. TypeScript has none, but to understand what the function returns you have to keep a careful eye on every return. Nesting, on the other hand, shows the direction of the computation unambiguously. In an imperative language it is clear anyway: code runs top to bottom. Let's rewrite the code, splitting the computation into two functions:

halveEven :: Int -> Maybe Int
halveEven x =
  let halved = x `div` 2
  in case even halved of
    False -> Nothing
    True  -> Just halved

halveAndScale :: Int -> Maybe Int
halveAndScale x =
  let halved = x `div` 2
  in case even halved of
    True  -> Just (halved * 10)
    False -> Just (halved * 5)

processNumber :: Int -> Maybe Int
processNumber x =
  case halveEven x of
    Nothing     -> Nothing
    Just halved -> halveAndScale halved

It may seem things only got worse, but they really haven't. Haskell already has a ready-made tool for this — the Monad class with its binding operator (bind):

class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b

processNumber :: Int -> Maybe Int
processNumber x = pure x >>= halveEven >>= halveAndScale

Beautiful? That's the point. There is also the so-called do notation. Here is the same example written with it:

processNumber :: Int -> Maybe Int
processNumber x = do
  halved <- halveEven x
  result <- halveAndScale halved
  pure result

Does this remind you of anything? The code really does look like a program in an imperative language, although its semantics stays monadic. A monad lets you explicitly control the sequence of dependent computations — that is its defining feature. And it does so beautifully.

Just like functors, monads come with laws of their own, and keeping them is the programmer's responsibility:

pure a >>= f  ==  f a                           -- left identity
m >>= pure    ==  m                             -- right identity
(m >>= f) >>= g  ==  m >>= (\x -> f x >>= g)    -- associativity

So much for the binding operator. Now let's see which monads show up in real code. Almost every familiar "imperative" device — exceptions, a shared config, a log, mutable state, I/O — has a pure monadic counterpart.

MonadContext / effectImperative counterpart
Maybe\mathtt{Maybe}the value may be absentnull + early return
Either  e\mathtt{Either\;e}an error with a causeexceptions
[]\mathtt{[\,]}nondeterminism: zero or more resultsnested loops
Reader  r\mathtt{Reader\;r}shared read-only environmentconfig
Writer  w\mathtt{Writer\;w}accumulating a loglogging
State  s\mathtt{State\;s}mutable statevariables
IO\mathtt{IO}the outside worldsystem calls: files, network, console

At first glance these monads solve completely different problems. But Reader, Writer and State are built very much alike — they are wrappers around functions or values with an extra context:

newtype Reader r a = Reader (r -> a)        -- environment -> value
newtype Writer w a = Writer (a, w)          -- value + log
newtype State  s a = State  (s -> (a, s))   -- old state -> (value, new state)

The binding operator of the State monad threads the state through the chain by itself — no more writing s1,s2,s3s_1, s_2, s_3\ldots by hand. The primitives: get::State  s  s\mathtt{get} \mathbin{\mathtt{::}} \mathtt{State}\;s\;s, put::s\mathtt{put} \mathbin{\mathtt{::}} s State  s  ()\to \mathtt{State}\;s\;().

Every executable Haskell program runs inside the IO monad: its entry point has type main::IO  ()\mathtt{main} \mathbin{\mathtt{::}} \mathtt{IO}\;(), and all interaction with the outside world is performed as IO actions. A value of type IO  a\mathtt{IO}\;a describes an action that, when executed, returns an aa. You cannot simply extract a pure value out of IO: there is no function IO  a\mathtt{IO}\;a a\to a.

So all these monads share a single interface — which is why one and the same do code works with different effects. And Haskell stays pure: effects do not happen behind your back; they are explicitly reflected in the types and bound into a controlled sequence of computations.