Skip to content

A Motivating Example

To explain what a monad is, let us focus on the Writer w functor mentioned in the introduction. Here's a simplified version of it, where we fix w = String:

newtype Writer a = Writer { runWriter :: (a, String) }

Remember, we can view this type as a functor—it stores a value of type a—or as a decorator—in addition to storing an a, it stores additional information of type String. You can verify that the following instance definition satisfies all the functor laws, so Writer is indeed a functor:

instance Functor Writer where
    fmap f (Writer (x, s)) = Writer (f x, s)

The decoration provided by Writer is rather explicit: it's an additional value of type String, and a value of type Writer a is always an a bundled together with a String. In the case of other functors, the decoration is less explicit.

A value of type Maybe a doesn't store any additional data. The decoration provided by the Maybe functor is the ability of a value of type a to be present or not. This clearly makes Maybe a different from a because a value of type a is just that, a value of type a. It cannot be absent. Maybe doesn't decorate its argument type in the sense that it stores additional information but in the sense that Maybe a is a richer type than a, one that has the ability to express the absence of a value.

A value of type [a] once again stores only as, possibly many of them. And that's the decoration—the enrichment—of the type a provided by the list functor. It's the ability to have an arbitrary number of values of type a; not exactly one of them, as with the type a; or one or none of them, as with the type Maybe a.

Remember, the Reader r functor:

newtype Reader r a = Reader { runReader :: r -> a }

The decoration provided by Reader r is the ability of a value of type a to depend on some context of type r. Conceptually, a value of type Reader r a is a collection of many values of type a, indexed using values of type r.

A monad is a functor equipped with two operators return and (>>=). When programming using a monad, we use the monad to decorate the return values of functions. Let's call functions that do this decorated functions. Since functional programming is programming by function composition, we need a composition operator for decorated functions. This is what the (>>=) operator of the monad provides.

Let's look at a simple example of these ideas in action, using our Writer functor. We start with two functions that use Writer to decorate their return values:

double :: Int -> Writer Int
double x = Writer (2 * x, "Doubling " ++ show x ++ "\n")

add1 :: Int -> Writer Int
add1 x = Writer (x + 1, "Adding 1 to " ++ show x ++ "\n")

Each function simply doubles its argument or increases it by one, but it also decorates the result with a string that explains how the return value was produced from the function argument. You can imagine that such functions can be useful for debugging purposes or when writing systems code1 that should add log messages to the system's log files.

Now, if we had undecorated versions of double and add1,

pureDouble :: Int -> Int
pureDouble x = 2 * x

pureAdd1 :: Int -> Int
pureAdd1 x = x + 1

then we could define

pureDoublePlus1 :: Int -> Int
pureDoublePlus1 x = pureAdd1 (pureDouble x)

or, more succinctly,

pureDoublePlus1 :: Int -> Int
pureDoublePlus1 = pureAdd1 . pureDouble

If we wanted to build a function doublePlus1 that combines double with add1 in the same way, then we would probably want it to concatenate the log messages of double and add1. After all, this is what it does. It first doubles its argument and then adds one to the result. Our log should allow us to trace these steps. We can build this function manually like this:

doublePlus1 :: Int -> Writer Int
doublePlus1 x = Writer $ let (y, msgDouble) = runWriter (double x)
                             (z, msgAdd1)   = runWriter (add1   y)
                         in  (z, msgDouble ++ msgAdd1)

This does exactly what we expect:

GHCi
>>> newtype Writer a = Writer { runWriter :: (a, String) }
>>> double x = Writer (2 * x, "Doubling " ++ show x ++ "\n")
>>> add1 x = Writer (x + 1, "Adding 1 to " ++ show x ++ "\n")
>>> :{
  | doublePlus1 x = Writer $ let (y, msgDouble) = runWriter (double x)
  |                              (z, msgAdd1)   = runWriter (add1   y)
  |                          in  (z, msgDouble ++ msgAdd1)
  | :}
>>> runWriter $ doublePlus1 3
(7,"Doubling 3\nAdding 1 to 6\n")
>>> runWriter $ doublePlus1 10
(21,"Doubling 10\nAdding 1 to 20\n")

But we have lost the ability to simply pass the result of one function to the next. Neither of the following would compile:

doublePlus1 :: Int -> Writer Int
doublePlus1 x = add1 (double x)

or

doublePlus1 :: Int -> Writer Int
doublePlus1 = add1 . double

That's because the types don't match: add1 expects a value of type Int, but the return type of double is Writer Int, a completely different type. To compose add1 and double, we had to remove the decoration—the log message—from the return value of double x, call add1 on the result, and finally combine the decorations.

We don't have any of this trouble in imperative languages, say in Python:2

logging.py
log = ""

def double(x):
    global log
    log += f"Doubling {x}\n"
    return 2 * x

def add1(x):
    global log
    log += f"Adding 1 to {x}\n"
    return x + 1

def doublePlus1(x):
    return add1(double(x))

When we run doublePlus1, the log holds the expected log messages afterwards:

Python
>>> import logging
>>> logging.doublePlus1(5)
11
>>> print(logging.log)
Doubling 5
Adding 1 to 10

In our Python implementation, we are able to just compose add1 and double because the effect of producing the log is exactly this, an effect. It happens when the function runs, but the effect is not reflected in the function's return value. Since Haskell doesn't allow us to have such hidden effects, we need to resort to something like our Writer functor to decorate function return values, and then it is up to us to combine the results and log messages correctly, as we did in our definition of doublePlus1. Programming that way is a pain though.

What Haskell does give us is the ability to define our own functions and operators. Hopefully, this will let us come up with a prettier implementation of doublePlus1. The key to the succinct definition of pureDoublePlus1 was the function composition operator:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

Can we come up with an operator that allows us to compose decorated functions? Let's call this operator (>=>). We want this operator to take g :: a -> Writer b and f :: b -> Writer c and produce from it a composed function g >=> f :: a -> Writer c which removes the decoration from g's return value, calls f on the undecorated value, and then combines the decoration removed from g's return value with f's decoration of its return value. This looks a lot like pure function composition using the (.) operator, only the return values of all functions involved are decorated using Writer:

f :: b ->        c    and    g :: a ->        b    ==>    f . g   :: a ->        c
f :: b -> Writer c    and    g :: a -> Writer b    ==>    g >=> f :: a -> Writer c

The order of the arguments is reversed: Instead of f >=> g, we write g >=> f, but that will make sense soon because we should think about something like f >=> g >=> h >=> k as a sequence of steps to be executed in this order.

The definition of (>=>) practically writes itself and looks remarkably similar to our definition of doublePlus1:

(>=>) :: (a -> Writer b) -> (b -> Writer c) -> (a -> Writer c)
(f >=> g) x = let (y, msgG) = runWriter (g x)
                  (z, msgF) = runWriter (f y)
              in  (z, msgG ++ msgF)

With this in hand, we can now define doublePlus1 more succinctly:

doublePlus1 :: Int -> Writer Int
doublePlus1 = double >=> add1

We seem to have gained very little. Sure, our definition of doublePlus1 is short and very similar to the definition of pureDoublePlus1 now, but we had to write the (>=>) operator that does exactly the same as our original implementation of doublePlus1. What we have gained is the benefit of refactoring: the (>=>) operator allows us to compose functions whose return values are decorated by Writer anywhere in our code; we don't have to manually implement the composition of Writer-decorated functions again and again and again.

Moreover, once we turn Writer into a proper monad, this composition operator will work quietly under the hood of Haskell's do-notation, which allows us to write code that looks a lot like imperative code. The Python implementation of doublePlus1,

def doublePlus1(x):
    y = double(x)
    return add1(y)

becomes

doublePlus1 :: Int -> Writer Int
doublePlus1 x = do
    y <- double x
    add1 y

So let's discuss the general concept of a monad next.


  1. Not that you'd ever write systems code in Haskell. That's what C, C++, Rust, and scripting languages are for. 

  2. This Python code uses global variables for something that could be done much more nicely using a Log class and by passing an object of this type to any function that would like to add to the log, but in the interest of keeping the example short, I opted for a quick and dirty solution with a global variable as a log.