# What's a monad?

Note: The following explanation is based on this post by Bartosz Milewski.

## Introduction

Monads are famously misunderstood. They've been likened to space suits, toxic waste and who knows what else. Depending on who you ask you might be told that monads are about:

- Order of evaluation
- Since languages that provide monads by default tend to be pure functional and declarative, we don't usually know the exact order of evaluation. Whatever else they may do, the main thing we use monads for, the story goes, is to enforce a particular order of evaluation in places where that is important.
- Side effects and IO
- Pure functional languages ban side effects so handling things like mutable state, errors and IO don't work the way most of us are used to. Monads allow us to do side effect-like things in a pure functional context.

Monads are used for these things but that's not really what they *are*
or what they're *about* and focusing too much on those aspects tends
to confuse the situation. What monads actually are is deceptively
simple: monads are function composition.

## A simple logging example

At least as far as programmers need to worry about them, monads are basically just a type of function composition. If you understand how function composition works, you pretty much understand monads.

So let's see how this works. For the purposes of explanation we need a way to compose functions. Pymonad has a Compose construct for this purpose but it's built on top of the Reader monad and I don't want to use a monad to explain monads. So we'll use this instead:

def compose(f, g): return lambda x: g(f(x))

Many languages that have a composition operator compose functions
right-to-left but for a named composition function I prefer the
semantics of composing left-to-right, so the above function
evaluates `f`

first and then feeds the result to `g`

.

Alright, so suppose we're writing some program and we have these two functions:

def add_7(x): return x + 7 def mul_5(x): return x * 5

And somewhere in our program we commpose those two functions like so:

composed_arithmetic = compose(add_7, mul_5) # ... more code ... some_var = composed_arithmetic(6) # result: 65

So far so good. The problem comes when we decide it would be great
if our functions created a log. We don't want to mutate global state
and it's not really the job of our functions to know anything about
the *entire* log so we just have our functions return a string along
with the result that we were returning before.

def add_7(x): return x + 7, 'Adding 7 to input {}\n'.format(x) def mul_5(x): return x * 5, 'Multiplying input {} by 5\n'.format(x)

But as soon as we do that, we've broken `composed_arithmetic`

.
`add_7`

is returning a tuple but `mul_5`

is expecting just a
number. Importantly: We still want to be able to compose these
functions like we did before; the overall behaviour of the system
should be the same just with added logging; we need to do it in a
way that deals with the logging information. So what do we do? We
change how composition works!

def compose_with_logging(f, g): def _compose_internal(x): fx, f_log = f(x) gx, g_log = g(fx) return gx, f_log + g_log return _compose_internal

And then we update the composition:

composed_arithmetic = compose_with_logging(add_7, mul_5) # ... more code ... # This doesn't change! some_var = composed_arithmetic(6) # result: 65, "Adding 7 to input 6 # Multiplying input 13 by 5"

Now you can add logging to any functions you want and if you were
composing those functions all you need to do is switch to using
`compose_with_logging`

and everything will work as expected.

If we must use metaphores when talking about monads then we should
at least use one that's relevant to programming. The functions above
return a value that we're actually interested in and some additional
metadata. In this case the metadata is logging information, in other
cases it might be error states, mutable state, or something
else. But in all cases adding metadata to the return types of our
functions breaks composition forcing us to *redefine* composition in a
way that takes the metadata into account.

That's a monad.

## All monad types are tuples in disguise

The above example is a simplified version of the Writer monad. In a sense, the monad's type is the tuple returned from each of the functions: (int, string).

It turns out that *all* monads can actually be expressed in the
general form (a, m), where *a* is the return value that you're
interested in and *m* is the 'metadata' which the monad deals
with. If you've played around with monads before though you'll know
that they don't always look like tuples. So what's going on?

The answer has to do with algebraic data types and how they can be manipulated into equivalent forms. That's beyond the scope of this article, but a quick example should give you a general idea.

Let's look at simple error handling. Our metadata will be booleans: True indicates that the returned value is valid and can be used in further computations; False indicates the value is invalid and should be ignored.

def add_7(x): # Errors won't happen in this function so we can always return a # valid result. return (x + 7, True) def not_zero(x): if x == 0: # It's an error if we get the value zero return (None, False) else: # But any other number is OK, we just pass it on. return (x, True) def compose_with_errors(f, g): def _compose_internal(x): fx, f_err = f(x) gx, g_err = g(fx) return (gx, f_err and g_err) return _compose_internal

This works much like the logging example but instead of
concatenating logs we use a logical `and`

to combine error
states. If either input function fails then the whole thing
fails.

But having to construct tuples all the time is error prone and has little semantic value. Instead we can implement functions to do it for us.

def Result(x): return (x, True) Error = (None, False)

Since the semantics of errors is that we ignore the returned value,
one `Error`

is just as good as any other and the value doesn't
matter. That's why we don't need an `Error`

function but can just
use a constant. Now we can write our functions like so:

def add_7(x): # Errors won't happen in this function so we can always return a # valid result. return Result(x + 7) def not_zero(x): if x == 0: # It's an error if we get the value zero return Error else: # But any other number is OK, we just pass it on. return Result(x)

The `compose_with_errors`

function still works exactly the same
way. So even though in this version the monad values don't *look*
like tuples, they really are in the background. The translation to
and from a tuple representation isn't always this straight-forward
but it is always possible.

## Generalizing

If you know a little bit about monads already you might be
wondering where `bind`

(the `>>=`

operator in Haskell) is in all of
this. It's actually hiding in plain sight.

When we wrote the logging example we had to write a
`compose_with_logging`

function. When we wrote the error example we
needed a `compose_with_errors`

function. And any other monads we
wanted to implement would require their own `compose_with_*`

function as well.

If we change `add_7`

and `mul_5`

to error tracking functions we
also have to change `composed_arithmetic`

to call the correct
compose function. That's kind of a pain. It would be nice if we
could change which *type* of composition we're doing without having
to change every call to a composition function.

Let's go back to logging for a moment. Instead of having the
functions return a tuple, let's have them return a `Logging`

object.

class Logging: def __init__(self, result, message): self.result = result self.message = message def add_7(x): return Logging(x + 7, 'Adding 7 to input {}\n'.format(x)) def mul_5(x): return Logging(x * 5, 'Multiplying input {} by 5\n'.format(x)) def monad_compose(f, g): # How do we define this? pass composed_arithmetic = monad_compose(add_7, mull_5) some_var = composed_arithmetic(6)

How do we define composition for functions of this type? The
resulting function has to take an integer as input and should return
a `Logging`

object as output. The input needs to be passed to the
first function:

def monad_compose(f, g): return lambda x: f(x) #...then what?

Next we need access to the result and message returned by `f`

so we
can apply it to `g`

. Since we need access to the internals of a
`Logging`

object let's make it a method:

class Logging: def __init__(self, result, message): self.result = result self.message = message def bind(self, g): # The infamous bind gx, g_log = g(self.result) return Logging(gx, self.message + g_log) def monad_compose(f, g): return lambda x: f(x).bind(g)

Now we've encapsulated the logic of dealing with logs into the
logging object itself and made `monad_compose`

^{1} entirely general. If we
want to change the functions to deal with errors we can create an
Error class with it's own `bind`

method to handle things correctly
and `monad_compose`

will continue to work properly.

Many explanation of monads start by trying to explain `bind`

and
that's where a lot of confusion creeps in. It has a strange type
signature and it's hard to explain exactly what `bind`

does since
it does something different for every single monad. But actually,
it's not so mysterious: `bind`

exists as a consequence of
generalizing `monad_compose`

and it's simply where we encapsulate
the composition logic for the type.

The reason so many explanations of monads start by trying to
explain `bind`

is because once you have `bind`

it's often
convenient to just use it directly instead of bothering with
`monad_compose`

.

Once we've got that `Logging`

class we can do something like this:

some_var = ( Logging(6, '') .bind(add_7) .bind(mul_5) )

`monad_compose`

creates a new function that you can use repeatedly
but even that can be done with bind:

composed_arithmetic = lambda x: Logging(x, '').bind(add_7).bind(mul_5) # Or def composed_arithmetic(x): return ( Logging(x, '') .bind(add_7) .bind(mul_5) )

So `bind`

is generally more useful than `monad_compose`

but
`monad_compose`

makes it clearer what the purpose of a monad
actually is: function composition.

## Footnotes:

^{1}

In pymonad.tools
this function is called `kleisli_compose`

.