What Monads make possible is the composition of functions that have side effects.

In Haskell, we denote a function f that takes as input a type a and gives as output a type b, as follows:

f :: a -> b

The composition of functions that have no side effects is straightforward. Given two functions f and g:

g :: a -> b
f :: b -> c

their composition f o g is defined as first the application of function g to a variable of type a and then the application of function f to the output of g. Thus the composition f o g is of type a -> c and is defined as (f o g) a = f ( g a ). Thus:

f o g :: a -> c
(f o g) a = f (g a )

where in this last equation, a denotes a variable of type a.

But the composition of functions that have side effects is not straightforward.

If, for example, M (this letter is chosen in order to think of/denote a Monad) is a type constructor which “elevates” types to another context, e.g. a context that has side effects, and we have the following two functions:

g :: a -> M b
f :: b -> M c

then the composition of these two functions is not straightforward. Compared to the two functions that had no side effects, these functions do not match: the output of g is of type M b, whereas the input of f is of type b (and not M b). Thus, we cannot perform a straightforward composition of these two functions. As a matter of fact, we have to tell our system/program how these functions can be composed. We have to provide the instructions of the composition ourselves.

The operator bind in Haskell is denoted as a side ways funnel >>= and “forces” the composition of two functions like f and g. But we have to provide the code for bind in order for it to be able to perform this operation.

So, in order to use >>= to compose the two functions, we would “force” or “bind” the output of g into the input of f. But, as I explained, the output of g is of type M b instead of b and b is the input type that f expects. That is why we will use bind instead of normal function composition.

Let’s see this in practice:

g :: a -> M b
f :: b -> M c

f composed with g is taking the output of g and passing it to f, as follows:

g >>= f

The output of g is of type M b and f is of type b -> M c. Thus, >>= is of type

M b -> (b -> M c) -> M c

This is because >>= takes the output of g which is of type M b and also takes the function f which is of type b -> M c and produces the output of f which is of type M c.

In order to implement bind, we have to provide the implementation of the higher order function of type

M b -> (b -> M c) -> M c

(which is exactly the type of bind).

By providing the definition of the function

M b -> (b -> M c) -> M c

we provide the way for the functions

g :: a -> M b
f :: b -> M c

to be composed.

Indeed, in order for these functions to be composed, the output of g which is of type M b, has to pass as input of the function f whose type is b -> M c. By implementing bind >>=, we tell our program exactly how this is done. And in this way, we provide the composition of functions with side effects, that is, functions not of type a -> b but of type a -> M b, where the type constructor M elevated the type it acts upon to a context/realm that can have any side effect we want.

In order to be mathematically aligned with Category Theory, the functions we provide have to respect composition and unit. In Haskell, we provide adherence to composition by implementing “bind” and we provide adherence to unit by implementing “return”.

So, by implementing “return” and “bind” we have true mathematical adherence to the standards of Category Theory for our functions, even though our functions contain side effects.

We saw that in Haskell, a Monad is a type constructor armed with the definitions of “return” and “bind” for it. In Category Theory, a Monad is defined differently, but equivalently. The definition of Monad in Category Theory and its correspondence in Haskell is a matter of importance but also beyond the scope of this blog post.