# Introduction

At the YOW! 2013 conference one of the designers of Haskell prof. Philip Wadler illustrated how Monads enable a purely functional programming language to perform inherently imperative operations like I/O and exception handling. Not surprisingly, there has been a lot of interest in the topic resulting in almost exponential growth of Monads tutorials and resources on the web. It is unfortunate that most tutorials exemplify Monads with functional languages, given that most “Monad newbies” are not experienced functional programmers. However, Monads are not specific to Haskell and other pure functional languages and can be used and exemplified in imperative languages as well, which is the goal of this tutorial.

**So how is this tutorial different from the rest out there?** This tutorial explains the
intuition behind Monads and demonstrates them with a few simple and short python examples.
Its goal is to explain Monads simply in less than 15 minutes and thus it refrains from making
insightful philosophical and theoretical reflections about
burritos,
space suits,
writing desks and endofunctors.

# Motivating Examples

We will look at 3 example problems for function composition. We will solve each of them in two ways - the standard imperative way and the monad way. Then we’ll compare the approaches.

## 1. Logging

Let’s assume we have the unary (single argument) functions `f1`

, `f2`

and `f3`

, each of which returns
an increment of its integer parameter. Additionally, each of them generates a readable log message,
representing its arithmetic operation:

1 2 3 4 5 6 7 8 | def f1(x): return (x + 1, str(x) + "+1") def f2(x): return (x + 2, str(x) + "+2") def f3(x): return (x + 3, str(x) + "+3") |

Now we would like to chain the functions `f1`

, `f2`

and `f3`

given a parameter `x`

- i.e. we want to compute `x+1+2+3`

.
Additionally we want to have a readable description of all applied functions.

One way to achieve this is:

1 2 3 4 5 6 7 8 9 10 11 12 | log = "Ops:" res, log1 = f1(x) log += log1 + ";" res, log2 = f2(res) log += log2 + ";" res, log3 = f3(res) log += log3 + ";" print(res, log) |

This solution is far from perfect, as we have repeated “glue code”, which accumulates the overall result and
prepares the input of the functions. If we add a new function `f4`

to the sequence, we’ll have to repeat the
glue code again. Moreover, manipulating the state of the variables `res`

and `log`

makes the code
less readable, and is not essential to the program logic.

Ideally, we would like to have something as simple as the chain invocation `f3(f2(f1(x)))`

.
Unfortunately, the return types of `f1`

and `f2`

are incompatible with the input parameter types of `f2`

and `f3`

.
To solve the problem we introduce two new functions:

1 2 3 4 5 6 | def unit(x): return (x, "Ops:") def bind(t, f): res = f(t[0]) return (res[0], t[1] + res[1] + ";") |

Hence, we can solve the problem with a single chained function invocation:

1 | print( bind(bind(bind(unit(x), f1), f2), f3) ) |

The following diagram depicts the computational process when `x=0`

.
By `v1`

, `v2`

and `v3`

we denote the interim values resulting from calling unit and bind:

The `unit`

function converts the input parameter `x`

into a tuple/pair of
an integer and a string. The subsequent invocations of `bind`

call their parameter
function `f`

and accumulate its result with the interim value represented by the `t`

formal parameter.

This avoids the shortcomings of our previous approach because the `bind`

function implements
all the glue code and we don’t have to repeat it. We can add a new function `f4`

by just including
it in the sequence as `bind(f4, bind(f3, ... ))`

and we won’t have to do other changes.

## 2. List of Interim Values

In this example we assume that we have three simple unary functions:

1 2 3 4 5 | def f1(x): return x + 1 def f2(x): return x + 2 def f3(x): return x + 3 |

As in the previous example we would like to compose them in order to compute `x+1+2+3`

.
Additionally we would like to generate a list of all interim and final values - i.e. `x`

, `x+1`

, `x+1+2`

, and `x+1+2+3`

.

Unlike the previous example, in this one the functions are composable as their parameter and result types match.
Therefore the simple invocation `f3(f2(f1(x)))`

will give us the desired value of `x+1+2+3`

.
However, this won’t generate the interim values.

A straightforward approach is:

1 2 3 4 5 6 7 8 9 10 11 12 | st = [x] res = f1(x) lst.append(res) res = f2(res) lst.append(res) res = f3(res) lst.append(res); print(res, lst) |

Again, this is not a very good solution, as we have glue code that takes care of aggregating the interim values in a list. If we add a new function f4 to the sequence, we’ll have to repeat the glue code for updating the list.

To overcome these issues, just like in our previous example we introduce two auxiliary functions:

1 2 3 4 5 6 | def unit(x): return (x, [x]) def bind(t, f): res = f(t[0]) return (res, t[1] + [res]) |

Now we can write the entire program as a single sequence of invocations:

1 | print( bind(bind(bind(unit(x), f1), f2), f3) ) |

The following diagram depicts the computational process when `x=0`

. Again `v1`

, `v2`

and `v3`

are
the interim values resulting from calling `unit`

and `bind`

:

## 3. Nulls/Nones

Lets try something a bit more interesting with classes and objects. Lets assume we have a class Employee with two methods:

1 2 3 4 5 6 | class Employee: def get_boss(self): # Return the employee's boss def get_wage(self): # Compute the wage |

Each employee instance has a boss of type `Employee`

and a wage, which can be accessed through
the two methods. Both of these methods can return None (i.e. the wage is unknown, or the employee does not have a boss).
In this example we want to develop a program, which given an `Employee`

instance `john`

returns his boss’s wage.
If the wage can no be determined or if `john`

is `None`

, we should return `None`

.

Ideally we would want to write something as simple as:

1 | print(john.get_boss().get_wage()) |

However, as the methods can return `None`

, this can result in an error. A simple workaround is:

1 2 3 4 5 | result = None if john is not None and john.get_boss() is not None and john.get_boss().get_wage() is not None: result = john.get_boss().get_wage() print(result) |

However, in this solution we are unnecessarily calling multiple times the `get_boss`

and `get_wage`

methods.
If they are computationally expensive (e.g. if they access a database) this may be undesirable. Hence, our solution should look like:

1 2 3 4 5 6 7 8 9 | result = None if john is not None: boss = john.get_boss() if boss is not None: wage = boss.get_wage() if wage is not None: result = wage print(result) |

This obviously is not pretty, since we had to write 3 bloated if statements to get to our result. To solve the problem, we resort to the same trick we used previously. We define the following two functions:

1 2 3 4 5 | def unit(e): return e def bind(e, f): return None if e is None else f(e) |

Now we can write the program in one line:

1 | print( bind(bind(unit(john), Employee.get_boss), Employee.get_wage) ) |

You probably noticed that we didn’t actually need to call `unit(john)`

, as it simply returns
its input parameter. We did it to keep to the previous framework/pattern, so we can define a generalised
solution later on. Also, note that in python methods can be used as simple functions. That is, instead of
`john.get_boss()`

we can write `Employee.get_boss(john)`

, which allows us to write the code above.

The following diagram illustrates the computational process if `john`

does not have a boss (i.e. `john.get_boss()`

returns `None`

):

# Generalisation - Monads

Lets assume we want to compose the functions f1, f2, … fn. If all input parameters match all return types,
we can simply use `fn(... f2(f1(x)) ...)`

. The following diagram depicts the underlying computational process.
`v1`

, `v2`

… `vn`

represent the interim results of the function invocation.

However, often this approach is not applicable. For instance in the Logging example the types of the input parameters and the returned values are different. In the 2nd and 3rd examples the functions were composable, but we wanted to “inject” additional logic between the function invocations - in example 2 we wanted to aggregate the interim values and in example 3 we wanted to insert None/Null checks.

## 1. Imperative Solution

In the above examples we first defined a straightforward imperative approach, which is depicted below:

Before calling f1, we execute some initialisation code. For instance, in Example 1 and 2 we initialised variables to store the aggregated log and interim values. After that, we call the functions f1, f2… fn and between the invocations we put some glue code. In Example 1 and 2 the glue code aggregates the log and the interim values. In Example 3, the glue code checks if the interim values are Null/None.

## 2. Enter Monads

As we saw in the examples, this straightforward approach has some unpleasant effects - repeated glue code, multiple Null/None checks etc.
In order to achieve more elegant solutions, in the examples above we used a design pattern with two functions (`unit`

and `bind`

).
This pattern is called **Monad**.
In essence, the `bind`

function implements the glue code and `unit`

implements the initialisation code.
This allows us to solve the problem in one line:

1 | bind(bind( ... bind(bind(unit(x), f1), f2) ... fn-1), fn) |

The following diagram displays the computational process:

The `unit(x)`

invocation generates an initial value `v1`

. Then `bind(v1, f1)`

generates a new interim value `v2`

,
which is then used in the subsequent call to bind - `bind(v2, f2)`

.
This continues until the final result is generated. Using this pattern, by using different `unit`

and `bind`

functions we can achieve various types of function compositions.
Standard Monad libraries provide predefined sets of ready to use monads (`unit`

and `bind`

functions), which can be used “out of the box” to implement different kinds of composition.

In order to compose the `bind`

and `unit`

functions, the return types of `unit`

and `bind`

, and the type of `bind`

’s first parameter must be compatible.
This is called the Monadic Type. In terms of the previous computational diagram, the types of all interim values `v1`

, `v2`

… `vn`

must be Monadic.

Lastly, repeating the calls to `bind`

again and again can be tedious and should be avoided. For the purpose we define an auxiliary function:

1 2 3 4 | def pipeline(e, *functions): for f in functions: e = bind(e, f) return e |

Then instead of:

1 | bind(bind(bind(bind(unit(x), f1), f2), f3), f4) |

We can use the shorthand:

1 | pipeline(unit(x), f1, f2, f3, f4) |

# Conclusion

Monads are a simple and powerful design pattern for function composition. In a declarative language they can be used to implement features of imperative languages like logging and I/O. In an imperative language, they can reduce and isolate the bloated glue code between function invocations. This article only scratches the surface and builds an intuitive understanding of Monads. To learn more you can explore:

comments powered by Disqus