When you begin to learn functional programming everybody is telling you that pure functions with no side effects are the only way to go. This is fine up to the point. Sooner or later you will hear about monads. This post tries to explain what the monad is and why you should use it.
To fully understand the examples here you should know at least basics of Scala language. You should
also know how to use
Option[T] type in Scala or at least know the Java’s
which I described here. In the section about pure functional languages I used a bit of
Haskell, but I tried to make the examples as clear as I could for those who don’t know it.
This is raw definition of monad. Please note that you don't have to fully understand it to read the following sections. You can safely skip to the Simpler definition section. Having said that, I encourage you to face this definition. It may help you later on.
Let’s start with something not simple at all! The monad definition:
We can say that M is a monad when:
- it is generic type
- there is a unit function
T => M[T]
- there is a flatMap function
(M[T], T => M[T]) => M[T]
To make things even worse, there are three monad laws:
- Left identity:
unit(x) flatMap f == f(x)
- Right identity:
m flatMap unit == m
(m flatMap f) flatMap g == m flatMap (f flatMap g)
Let me clarify this definition a bit. Leaving the laws for later let us jump back to the M type
and the unit and flatMap functions. Type M is just regular generic type like
Try[T]. The unit function takes the value of type T and wraps it with a monadic type. For
Option[T] the unit operation is simple
Some(t: T). Lastly - the flatMap. Literature refers
to it as the bind operation. I decided to call it flatMap here so it sounds more familiar. It,
of course, takes a monadic value
M[T] and a function
T => M[T] and returns another
basically means that we can do some computation on value that is inside the monad and create a new
monad with the result of that computation. Scala is object oriented so
flatMap is just a method
and takes one parameter (the function) the other being just this object.
To sum this up with an example based on type
- we have got generic type
- we have unit function
- we have bind/flatMap method
Option.flatMap(f: T => Option[T]): Option[T]
Going back to the monad laws. The first two laws are fairly simple and describe the relations
between unit and flatMap. The associativity law tells us just that the order of the flatMap
doesn’t matter. We can write the laws for
Option[T] as following:
Some(t) flatMap f == f(t)
opt flatMap Some[T] == opt
(opt flatMap f) flatMap g == opt flatMap (t => f(t) flatMap g)
To prove that the laws hold we should replace our methods with their implementations to see if we can end up with the other side of equation. This could be a topic for whole new post (and maybe will be) so I’ll skip this for now.
The reality is that these monad laws are mainly important when you want to implement a monad yourself or you need to rely on one of these properties in your code. In fact when talking about monads most developers are thinking just about generic type M[T] with unit and flatMap operations.
There is even more! There are types that we call a monads but they do not satisfy those laws! Let’s
Try[T] for example. This monad is used to deal with possible exceptions that might occur
while processing. If you have some operation
expr: T that can throw some exception you
may want to wrap it with
Try(expr). This will return
Success(t: T) containing the result of the
Failure(ex: Throwable) with the exception that was thrown by that function. If you
closely examine all monad laws you can see that the left identity law does not hold.
unit(x) flatMap f == f(x)
This tells us that following should be true:
Try(expr) flatMap f == f(expr)
The law works fine if everything goes smooth and no exception is thrown. The problem pops up when
expr throws an exception. The left hand side
Try(expr) flatMap f never throws an
exception and just returns
Failure(ex). The right hand side
f(expr) will just throw the
exception so the law does not hold thus
Try[T] is not precisely a monad, but that is not a problem
for us. We are not mathematicians (no offense ment!). We just want things to work :)
So what does all this means for developers?
Monadic structure gives us, developers, a uniform way of defining a chain of transformations on virtually any type. Just look at this:
List('a', 'a', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'e') // List[Char] .groupBy(identity) // Map[Char, List[Char]] .mapValues(_.size) // Map[Char, Int] .reduceLeftOption((a,b) => if (a._2 > b._2) a else b) // Option[(Char, Int)] .map(_._1) // Option[Char] .foreach(println)
The above code finds the most popular element in the list. In this example the most popular element
c because it appears 4 times. I’ve defined a chain of transformations to find this value. The
most interesting thing about that is that the type I’m operating on changed three times, but the
chain still looks uniform! We start with a
List[Char] after calling
groupBy method we have a
Map[Char, List[Char]]. Then we replace the lists with just their sizes so after
Map[Char, Int]. With
reduceToOption we find the element of the map that has the biggest
value and create
Option[(Char, Int)]. Then we just get the first element from the tuple (this is
our most popular char) with
map and the final type is
Option[Char]. Last operation is just
printing the value (if found) to the standard output.
We used three different monads: List, Map, and Option. Every line of code changed the output type. Yet we still could invoke new transformations like we didn’t care! I personally think that this is fantastic :)
You may have noticed that I didn’t use the
flatMap method. In fact every method that I used above
CAN be implemented using one or more
flatMap calls. Let’s take simple
map for example:
m map f == m flatMap (x => unit(f(x)))
These methods are just some combinations of
flatMap that are useful and were given a name. Of
course they are not implemented with
flatMap in Scala because it would hurt the performance, but
the point is that they are the result of those types (List, Map, Option) being a monads.
Word about functional programming
This part contains dangerous amounts of Haskell.
Scala is kind-of functional programming language. You can write some parts functionally and other imperatively. This is great because by mixing the styles we can end up with code that is both readable and concise. Monads have another side which is extremely important for pure functional languages like Haskell.
You see, in pure functional languages you cannot define the order of operations. You can just define some equivalences. What do I mean? Take a look:
def add(a: Int, b: Int) = a + b
We are used to call this a function definition but what really happens here? We just tell that the
add(x,y) string can be replaced with
x + y string on the code level. In fact we could evaluate
pure functional code just by replacing strings!
Going back to the monads. In pure functional languages to define the order of computation you would have to invoke a function on a call to function etc.:
You can see that here we have to evaluate
h(x) then pass the value to
g and finally to
to imagine bigger program written like that. Well yes, it would be unreadable!
Now we could scream: Monads to the rescue!. But let’s not. If you look again at the code to find the most popular element in list you can see that we strictly defined the order for the operations.
This is exactly what we use in Haskell to pretend that we are doing imperative code. Of course
Haskell has some syntactic sugar on top of it so instead of writing
flatMap (which in Haskell is
>>=) you can write it imperatively-ish. The
do x <- thing1 y <- func1 x thing2 z <- func2 y
What is going on here? The
thing2 are monadic values (we can call
flatMap on them).
func2 returns a monadic values. What is going on here is that first we get the
thing1 and name it
x. Then we pass it as a value to the
func1 which returns another
monad. Then we take value from the monad and name it
y… You see where this is going. This
clearly defines the order of operations.
Example above is exactly the same as:
thing1 >>= (\x -> func1 x >>= (\y -> thing2 >>= (\_ -> func2 y >>= (\z -> return z))))
You see that the
do notation is a bit more readable :) This example is taken from Haskell
The state and side effects
Another thing is that in Scala we can talk about mutable and immutable state. In pure functional programming there is no state at all! There are only arguments passed to functions. That’s the closest thing to state you can get.
But there is state in the world. Our hard drives have state. Keyboard has state. There is a lot of state everywhere! How to deal with this in Haskell? You guessed it - monads. Lets look at this quick example:
main :: IO () main = do c <- getChar putChar c
This program, as you might suspect, reads one character from the standard input and writes it to standard output. To put it simply: awaits for keyboard button to be pressed and prints the letter to the console.
Here you can see that
getChar does have some kind of state as the value seems to materialize from
nothing - it doesn’t expect any arguments. So what happens here? Well - the input/output operations
are wrapped with a monad which acts here as a gate between our stateful world and the world of pure
We can rewrite it using
flatMap (which in Haskell is
main :: IO () main = getChar >>= putChar
In Scala it would look like:
def main(): Unit = getChar.flatMap(putChar)
getChar function returns a monad. If we invoke
flatMap on it gives us a key pressed on the keyboard as a
parameter to our function. Our function here is
putChar. It takes one char and returns a monad
back. The returned monad is empty (like
Unit in Scala) so the value is not interesting. The
putChar function does something else behind the scenes. It writes the character to the standard
output. This is side effect that we wanted. The ability to talk to stateful world.
All this is thanks to humble
Thanks for reading! I hope that you will not be frightened by the monad word anymore! These are useful little creatures. They are easy to use when you get the hang of them, but quite hard to learn (and explain!). I really hope that this post was helpful to you because when I wanted to learn monads the first time I couldn’t understand a single thing about them. Then something just clicked and everything was clear. I hope that it just clicked for you today :)
If you have any questions please leave a comment below!