Pragmatic explanation of Monads, the ‘short short version’
This is a blog post that tries to give a pragmatic explanation about what a monad is, and what problem it tries to solve. This post is written by Ties van de Ven and Justus Brugman.
When I tried to learn about functional programming, I found myself lost in new words and jargon, I did not know about. Suddenly my vocabulary needed to be extended with terms like Monads, Monoids, Composable, Functors and so on. When trying to understand these terms, it became clear that I found it hard to understand what a monad is.
From Wikipedia: “In functional programming, a monad is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic. Monads achieve this by providing their own data type (a particular type for each type of monad), which represents a specific form of computation, along with one procedure to wrap values of any basic type within the monad (yielding a monadic value) and another to compose functions that output monadic values (called monadic functions).”
Wow, well I did not quite get that. So I went back to google to search for a more easy to understand explanation, then ran into 2 nice laws or definitions: . Soroush’s Burrito Law in the process: anyone trying to explain monads is going to fail, even if they account for how hard explaining monads is. . The definition of Douglas Crockford’s: Monads are “something that once developers really manage to understand, instantly lose the ability to explain to anybody else”.
Now that really made me confident. Perhaps diving a bit deeper into the category theory behind it. From Wikipedia: A monad is a certain type of endofunctor. For example, if F and G are a pair of adjoint functors, with F left adjoint to G, then the composition G o F is a monad. If F and G are inverse functors, the corresponding monad is the identity functor. In general, adjunctions are not equivalences—they relate categories of different natures. The monad theory matters as part of the effort to capture what it is that adjunctions 'preserve'. The other half of the theory, of what can be learned likewise from consideration of F o G , is discussed under the dual theory of comonads.
So I was lost… Time to do a pragmatic approach, and dive into the short short version now:
A monad is just a wrapper around a type or data structure.
So by ‘wrapping’ a class type (e.g. Integer) inside the Java Optional type, we add some functionality around it, like of(x)
, isPresent()
, orElse(c)
and get()
.
To be more precise, a Monad always contains a bind() function, That translates to flatMap in Java
In functional languages, there is a data type called the option type, also called maybe. The purpose is to encapsulate a value, that is returned from a function. This return type may indicate that there is no return value at all.
Optional<Integer> optionalYear = Optional.of(2016);
optionalYear.filter(y -> y == 2016).isPresent(); // returns true
optionalYear.filter(y -> y == 2017).isPresent(); // returns false
Okay, perhaps this is a bit to short, we need to adhere to a few monad laws to actually say we have a monad, so extend the explanation a bit, looking at Java’s Optional:
Left identity
The value within the monad can be transformed within itself.
It guarantees that applying a monad to a value will just wrap it, neither the monad or the value will be changed.
That would translate into Java’s Optional: Optional.of(1)
Function<Integer, Optional> add = y -> Optional.of(y + 1);
Optional.of(5).flatMap(add).equals(add.apply(5)); // returns true
Right Identity
If we have a monad and bind that monad’s return method, it is the same as the original wrapped value. This can be translated to Java’s Optional flatMap (bind): Optional.of(1).flatmap(f).
Optional.of(5).flatMap(Optional::of).equals(Optional.of(5)); // returns true
Associativity
If we have a sequence of functions applied to a monad it doesn’t matter how they’re nested
Function<Integer, Optional> addOne = y -> Optional.of(y + 1);
Function<Integer, Optional> addTwo = y -> Optional.of(y + 2);
Function<Integer, Optional> addThree = y -> addOne.apply(y).flatMap(addTwo);
Optional.of(5).flatMap(addOne).flatMap(addTwo).equals(Optional.of(5).flatMap(addThree)); // returns true
Note, Optional is not behaving like a true monad but we’ll learn to live with it.
The problem
So what is the problem that a monad solves (in practical terms)? The underlying problem is that quite a few methods have more than 1 return type. This may sound weird at first, but let me explain. Given the following function:
public Double divide(double a, double b) {
return a/b;
}
There are 2 possible outcomes, either the result is correct, or if b
equals 0
you are dividing by zero, which is not possible.
So apart from our Double
return type, we also need something to return in case there is a faulty state (b
== 0
).
In Java there are a few ways to approach this problem.
//Return a default value
if(b == 0){
return -1; //actual value that will compute, but might cause weird errors
}
//Return null
if(b == 0){
return null; //causes nullpointers
}
//throw an exception
if(b == 0){
throw new RuntimeException(); //Kill the current thread
}
All of these have the issue that the faulty return type is effectively hidden. The faulty return type has to either be documented (and read by the developer) or the developer needs access to the source code (and actually read it). This is where using a monad comes in.
public Optional<Double> divide(double a, double b) {
if(b == 0){
return Optional.empty();
}
return Optional.of(a/b);
}
Now we explicitly put the faulty return type in the function signature, and it cannot be ignored.
The 'disadvantage' now, is that we actually have to decide what happens during a fault situation.
Luckily the Optional (and other monads) has a pretty API to deal with this.
It has a divide(1,0).orElse(0)
to supply a value if something went wrong, and it even has a divide(1,0).get()
function if you want the old behaviour of throwing an exception.
So if your function has a 'hidden' second return type, you might want to take a look at what monads can offer you.