Don’t fear the Monad

If you have been working in Scala for a while, for sure you ended up in an argument about Monads. Some people love them some people hate them, often without a valid reason.

Monads are one of the most useful and powerful tools we have in typed functional programming, unfortunately though they seem to have a very bad reputation in many communities, I heard many times people saying that they are “too complicated” even if they really aren’t.

I suppose that the FP community shares a big part of the responsibility for this, the (in)famous sentence “A monad is just a monoid in the category of endofunctors, what’s the problem?” represents the problem quite well,
often if you read a discussion in some FP forum, you won’t understand half of what they are talking about even if you have already some experience and many conversations are a bit too abstract.

Anyways this is enough ranting about Monads, so
let’s talk about something else, let’s talk about Behaviour.

Behaviours

Software developers love to talk about Behaviour right? At some point indeed everyone was just talking about BDD.

When we code basically what we do is just encode some kind of behaviour, now we can define two categories of them, some are unique behaviours that just represent your business logic, and some are instead really common, and even if we talk everyday about principles like DRY, we simply repeat those ones over and over again everywhere.

Let’s show a couple of examples to explain what I mean, how many times did you see this bit of code ? (maybe in different languages but it’s always the same) :

public String printFoo(Object foo) {
if (foo != null) {
return foo.toString();
} else {
return "foo is null";
}
}

That is probably the most common but there are many others, for instance:

Thread thread = new Thread() {
public void run() {
System.out.println("Thread Running");
}
};

thread.start();

Both these examples represent a behaviour, the first a possible null value that we want to handle and the second an asynchronous computation.
Unfortunately this is a very good way to introduce bugs, for instance in the first example is very easy to forget to check the value in every place where it is used, you need to rely on the documentation to know if a value can be null or not, and you alway end up with NullPointerException.

Now in Scala and many other languages we have a type system, so why not use it to our advantage?

If we can encode a behaviour with a type, we can rely on compiler to help us avoid mistakes and it will also be our documentation, when we see that type we’ll know what’s going on.

Luckily in Scala those two behaviours (and some other) have already been encoded with a type, in this case we have Option and Future , let’s see how we can rewrite the previous examples, we’ll see later how map works:

// Any is the Scala version of Object
def printFoo(foo: Option[Any]): String = {
foo.map { fooValue =>
fooValue.toString
} getOrElse(
"foo is None")
}
Future { 
println(“Thread Running”)
}

Now why is this better than the previous implementation? 
Let’s start with the Option, the first thing here is that when you call the method printFoo you know already that foo is an optional value, you’ll have to handle the None case and you don’t have to rely on the documentation, and if you forget to make it an Option the compiler will remind you, and if you pass foo to another method, you don’t have to check if the method can handle the None case or not, you’ll know it from its definition.

The example with Future is similar, the syntax is way shorter, but more important, all the complicated logic needed to wait for the value to be ready, is handled by the future, so if you want to get the result of the operation you don’t have to deal with that.

Let’s see how that would work:

// download a web page from his url
def getPage(url: String): Future[String] = Future {
..
}
def main() {
val page: Future[String] = getPage("http://myurl.org")

// let's print the page
page.map { p => println(p) }
}

We wrapped a blocking computation inside a Future so it will run in another Thread, now to use the value inside the Future all we have to do is call map and pass the function that we want to apply to this value (see below for the map method definition). The Future will take care of all the complex logic behind, it will await for the computation to be completed and when it is, it will apply the function we passed to the resulting value. Creating a Thread manually and waiting for it to complete to get its result is quite hard to do and error prone, having a type to encapsulate this logic is very helpful.

Let’s take a look now at some similarities between Option and Future:

// Use Options together 
def getFirstName(user: User): Option[String] = { .. }
def getLastName(user: User): Option[String] = { .. }
def getFullName(user: User): Option[String] = {
getFirstName(user).flatMap { firstName =>
getLastName(user).map(lastName => s"$firstName:$lastName")
}
}
// Scala offers a syntactic sugar to write the same thing in a more // readable way
def getFullName2(user: User): Option[String] = {
for {
firstName <- getFirstName(user)
lastName <- getLastName(user)
} yield (s"$firstName:$lastName")
}
// Use Futures together 
def concatWebPages(url1: String, url2: String): Future[String] = {
getPage(url1).flatMap { page1 =>
getPage(url2).map(page2 => s"$page1:$page2")
}
}
// Again with the syntactic sugar
def concatWebPages2(url1: String, url2: String): Future[String] = {
for {
page1 <- getPage(url1)
page2 <- getPage(url2)
} yield (s"$page1:$page2")
}

As we see, those two examples are almost the same, we can use Option and Future and many other “behaviour” types in the same way, they all support these two methods:

 map[B](f: (A) ⇒ B): Behaviour[B] 
flatMap[B](f: (A) ⇒ Behaviour[B]): Behaviour[B]
  • map takes a function that will be applied to the current value contained in the Behaviour[A], returning a new Behaviour[B] with the new value
  • flatMap is similar to map, but it takes a function that returns a new Behavior[B] instead of a simple value and will “squash” the two behaviours together, this is really useful to chain together multiple behaviours of the same type, as we can see in the example
  • Another thing to notice is that because these methods are so common, in Scala we have a syntactic sugar to write them in a more readable way which is the for {} comprehension, we can see that in the example

Other than map and flatMap, both provide a way to create an instance from a value, in this case Some(v) to create an Option and Future(v). They also have the same “shape”, both classes are defined with a single type parameter Future[T] and Option[T].

So now as sensible developers, what do we do when more classes have common methods? We extract an interface!

trait Behaviour[T] {
def wrap[T](t: T): Behaviour[T]
def flatMap[R](f: T => Behaviour[R]): Behaviour[R]
// map can be implemented in terms of wrap and flatMap
def map[R](f: T => R): Behaviour[R] = flatMap(t => wrap(f(t)))
}

Ok so we have an interface that takes one type parameter and has two methods that we have to implement to represent our behaviour - so far quite simple no? Well let me rewrite this:

trait Monad[T] {
def point[T](t: T): Monad[T]
def flatMap[R](f: T => Monad[R]): Monad[R]
// map can be implemented in terms of wrap and flatMap
def map[R](f: T => R): Monad[R] = flatMap(t => point(f(t)))
}

Yes that’s it! This is what Monads are, just an interface with two methods that somehow is considered very hard to understand. The only differences with our Behaviour interface are that wrap is usually called point or pure and that in FP behaviours are usually called effects. 
This is a list of common monads in Scala with their effect:

List // Multiple values 
Future // Asynchronous computation
Option // Optional value
Try // Value or failure
Either // Value of one or the other type

Monads don’t compose

This is one of the main Monads limitations, and I think one of the most confusing for beginners (at least from what I have seen in Scala), basically you can’t use different monads together, for instance this won’t work:

for { 
firstName <- getFirstName(user) // Option[String]
page <- getPage(user) // Future[String]
} yield (s"$firstName:$page")

There is a good reason for this, the flatMap method is completely different for every monad depending on its effect so they can’t work together, and also, which should be the result type? Anyways no worries, when you have more monads stacked together like Future[Option[T]] there are ways to work with them without doing flatMap(flatMap(flatMap(…))), but that is another post.

Laws

Ok, I missed, intentionally, a few other things about Monads, the main is that to be a Monad there are few properties (or laws) that your implementation should respect. There are libraries that have tests for those laws available if you want to implement your Monads, but probably for your daily job knowing those laws won’t be required.

Conclusion

I hope with this article to demonstrate that Monads are actually a very simple concept with a bad reputation. Many people are using them already even without knowing what they are so hopefully this article will help to 
shed some light on them and I’ll see more in many codebases :)