Little programs with a twist!

Monads are a pretty complex concept to get your head around. This is often because they’re explained in a very theoretical sense. The typical explanation of monads you’ll find around the Internet feels like walking in the middle of an advanced maths lecture while drunk after having skipped half a semester’s worth of classes. Suffice it to say that after that lecture you’ll be pretty confused and feel like you’re missing a lot of the information required to understand anything. I hope that this article will be more like a talk with that one guy you know that can make quantum mechanics straightforward.

If there’s one thing that you need to know about monads is that they’re a tool for building little programs with a twist. The little programs are built using something called monadic expressions and the twist is controlled by the monad itself.

Monadic expressions are written in a special syntax that directly supports the use of monads. This syntax varies from language to language, but it generally looks like your typical procedural program. In the literature, you’ll often read that monads are used to build computations or that they’re all about composition. Well computation and composition are just other words for programs. After all, programs are used to compute things and they’re built by composing other smaller programs.

Earlier I mentioned that monads were the things that added the twist to these little programs we write using monadic expressions. Well, what are these twists that I’m talking about? How can a program have a twist?

Let’s first look at what we would consider a normal program. A normal program reads some inputs, does a number of computations on that input and then produces some output. If you want this program to do some asynchronous work, check that values exist or handle errors, you need to write the code to handle that.

Now, imagine a simple program that doesn’t have any special code to handle asynchronous work, null checking or error handling. What if this program were to somehow automatically work asynchronously, check for missing values or handle errors. This is the twist that I was talking about. It’s what makes a simple program with no code that does asynchronous work, checks for missing values or handles errors magically do all of those things. The twist is that magic.

Before I continue I should mention something important. So far, I’ve given 3 examples of twists: asynchronous work, checking if values are missing and handling errors. These are not the only things that monads can do. There’s a near infinite number of monads that can do just about anything.

The basics

Let’s start by looking at the basics of monads and monadic expressions. We’re going to try to avoid the implementation details and just look at the concepts.

Simply put, a monad is a type that supports a few operations and those operations respect a few rules. All you need to know about these operations and the rules they follow is that they’re used by the monad to play nice with monadic expressions. In other words, monads have to implement a contract that is used by monadic expressions.

If something is said to be a monad, it only means that it can be used in monadic expressions. It doesn’t tell you anything about how you can create the monad or interact with it outside of a monadic expression. It also doesn’t tell you anything about the monad’s twist. This means that if you want to use a monad, you’ll probably need to look up and read its documentation.

Monads are generic types. They’re type signatures look something like M[A]. Note that this is Scala syntax. Here, M is the name of the monad type and A is a type parameter, generic type or type template. Looking at this, you could say that monads wrap another type. This is not necessarily the case in practice, but for simplicity’s sake we can just assume that they do.

Like I mentioned before, monadic expressions are a neat bit of syntax that makes use of monads. The syntax itself varies from language to language but it looks something like this.

for (
  a <- mA
  b <- mB
  c <- mC
) yield doSomething(a, b, c)

There are usually two parts to a monadic expression. You have a sequence of bindings and a yield or return statement.

A binding is kind of like an assignment. In the syntax above, any of the x <- mX expressions are bindings. The value to the right of the arrow is an instance of a monad and the value to the left of the arrow is a new variable. This variable contains the value that is wrapped inside the monad. You can think of a binding as extracting the value out of a monad. This implies that the type of the variable is the same type as the one the monad wraps.

The yield or return statement is what returns the output of the monadic expression. You should note that monadic expressions return monads. You should also note that the expression to the right of the yield statement does not return a monad. It just returns a regular value. This means that the yield statement wraps the value into a monad.

We’ve already learned that the monad is what controls the twist of our monadic expressions. We also know that there are many kinds of monads out there. This brings forward a very interesting question. How does the monadic expression know which monad to use? Generally, there are two approaches.

  1. It figures it out using the type system.
  2. It requires you to specify which monad to use.

I’m going to focus on the first option because it’s the most commonly used one and it’s the one used by Scala. To figure out which monad to use, the monadic expression looks at the type of the monads used on the right side of bindings.

This is a little abstract so we’ll look at an example. In the monadic expression above the types of mA, mB and mC are used to determine which monad to use. Let’s say that the type of mA is Option[Int], the type of mB is Option[String] and the type of mC is Option[Double]. (Option is a monad we’ll see in the next section.) In this case the Option monad is used. You’ll notice that all of the monads are of the same type, but the wrapped type is different. This is important. For a monadic expression to work, all of the monads used in bindings have to be of the same type. Also, the wrapped type is not important. You can basically have anything as the wrapped type.

This means that you can’t directly mix different kinds of monads. There are other less direct ways of mixing monads. You can use monadic expressions inside of other monadic expressions and you can convert one type of monad to another. I won’t be covering these topics in this article. I just want you to keep this in mind.

Some practical examples

Now that we have some of the basics, we can look at a few types of monads and how we can use them.

We’re going to work with pretty simple but not trivial example. We’re going to write a program that fetches the biggest contributor of a Github user’s most popular repository. To make our life easier we’re going to use the following functions.

def getGithubUser(name: String): M[User] = ...
def getMostPopularRepository(user: User): M[Repository] = ...
def getBiggestContributor(repo: Repository): M[Contributor] = ...

We’ll ignore the implementation details here. These will change depending on the monad that we use. The gist of the code will be the same. The part that will change, is the way we create an instance of our monad.

You’ll notice that the return type of the functions are M[User], M[Repository], M[Contributor]. I’m using M as a placeholder for the monad that we’ll be using.

Option

Option (sometimes called Maybe) is a monad that helps you deal with missing values. By wrapping a type of a value with Option, you’re indicating to anyone using that value that it might be missing.

People sometimes achieve something similar by making use of the null value. This approach works, but it’s problematic. It forces anyone using your code to explicitly check for null manually. If they forget, the code will blow up. Because of this, people end up putting null checks everywhere.

Using Option is better than using null because of 2 reasons. First, when you try to pull a value out of an Option, you have to handle the case where the value is missing. This way, you can’t forget. Second, when you set the type of a value as Option, you are explicitly stating that the value may be missing. People using your code will immediately know that the value can be missing by looking at the type. This should prevent people from putting checks everywhere.

The really cool thing with Option is that when it’s used in a monadic expression it will handle missing values automatically. When you use a binding to extract the value out of an Option, if the value is missing the whole monadic expression stops and returns an empty value, otherwise it keeps going.

Let’s look at our example using the Option monad and a monadic expression. Note that in this example all the functions we defined earlier return an instance of the Option monad.

for (
  user <- getGithubUser("john.doe")
  repository <- getMostPopularRepository(user)
  biggestContributor <- getBiggestContributor(repository)
) yield s"$user/$repository: $biggestContributor"

You’ll notice that we’re never checking for an empty value in the monadic expression. If any of the functions we defined earlier return an empty value, the whole expression will stop and return an empty value. On the other hand, if all of our functions return a value, the expression will return the actual result.

Either

Either is a neat type. It allows you to specify that a value can be one of two types (usually called the right and left types). Because the value can be one of two types, it forces you to handle the possibility of either type being there. What’s neat about this is that it allows a value to have two different types in a completely static and type safe way.

Either can be used to do error handling. It allows you to write a function that returns either an error type or the type it would normally return. Earlier, I mentioned that Either forced you to handle the possibility of either type being there. This means that you have to handle the possibility of an error when you want to use the value. This is both good and bad.

This is good because it lets you express that your function might return an error in the type system and it forces you to handle these errors so that you don’t get any nasty surprises.

This is bad because it leads to a bunch of boilerplate and ugly code. This happens when you want to make calls to multiple functions that possibly return errors. In such a case, for every call, you have to check if the previous call returned an error. If it did, you can either handle the error or return it. If it didn’t, you can move on to the next call.

The good news is that Either is a monad. The Either monad has a bias. It prefers one type over the other. In the case of error handling, this would be the regular type. Earlier, I mentioned that Either had a left and right type. Usually, Either has a bias towards to the right type.

In a monadic expression, when you extract the value out of an Either monad there are two things that can happen. If the type of the value is the left type, the monadic expression stops and returns that value. If the type of the value is the right type, the monadic expression continues. This is similar to how the Option monad works.

Let’s look at our github example from earlier. In this case, all of the functions return either an error or the type they would normally return. The normal type is the right type and the error is the left type. Our code would look like this.

for (
  user <- getGithubUser("john.doe")
  repository <- getMostPopularRepository(user)
  biggestContributor <- getBiggestContributor(repository)
) yield s"$user/$repository: $biggestContributor"

You’ll notice something very interesting about this code. It is exactly the same as the one for the Option monad. In fact, I just copied the code from earlier. This makes sense because we’re doing the same thing as in the Option monad example. The only thing that changed was the twist. With Option, it was empty value checking. Now, it’s error handling.

Eventually

Eventually (sometimes called Promise or Future) is a monad that lets you deal with asynchronous code. It represents a value that will be computed at some point in the future. It’s used as a placeholder for the value until it becomes available at some point in the future.

There is more than one way to implement Eventually because there is more than one way write asynchronous code. For example, you could have your code block whenever you try to extract the value out of an Eventually monad. This works, but it’s not great. Blocking can slow things down and lead to deadlocks.

Because of this, Eventually is often implemented using callbacks. In such implementations, every instance of Eventually has a callback. This callback is called whenever the value becomes available. The value itself will be passed as an argument to the callback.

With this, you can write asynchronous code without blocking. The problem is that you always have to set callbacks which can easily lead to callback hell. We can use monadic expressions to solve this.

In a monadic expression, when you extract the value out of an Eventually monad, it sets the callback as the rest of the monadic expressions. Of course, the monadic expression returns an instance of Eventually.

Let’s looks at our github example again. This time, all the functions return an instance of Eventually. Our code would look like this.

for (
  user <- getGithubUser("john.doe")
  repository <- getMostPopularRepository(user)
  biggestContributor <- getBiggestContributor(repository)
) yield s"$user/$repository: $biggestContributor"

Again, the code is exactly the same as what we had before. This makes sense since we’re still doing the same thing and the only thing that’s changing is the twist.

Collection

You’ve probably heard of collections before. They represents 0, 1, or multiple values. As it turns out, collections work really well as monads.

One operation that is very common with collections is to apply an operation to every element of a collection and return a new collection containing the results of the operation on each element.

In regular procedural programming, you would do this with a for loop.

import scala.collection.mutable._

def doSomething(list: Seq[Int]) = {
  val res = MutableList[Int]()

  for (n <- list)
    res += n * 2

  res
}

You can do the same thing with multiple collections. This is done by nesting for loop. You would have a for loop for every list.

import scala.collection.mutable._

def doSomething(l1: Seq[Int], l2: Seq[Int], l3: Seq[Int]) = {
  val res = MutableList[Int]()

  for (n1 <- l1)
    for (n2 <- l2)
      for (n3 <- l3)
        res += n1 + n2 + n3

  res
}

You can also do this with functional programming using the map and flatMap functions nesting them in a similar way as the for loops.

This is not a very elegant way of doing things. It’s a lot of code for something that should be really simple and your code shifts more to the right, the more lists you’re working with.

You can solve this problem by using collections in monadic expressions. When you extract a value out of a collection in a binding, you’re basically nesting a for loop. This allows you to take a bunch of nested for loops and turn them into a flat monadic expression.

Let’s look at out github example. In this case, we’ll need to change our functions a little bit. We’ll be using this slightly modified version.

def getGithubUsers(namePattern: String): Seq[User] = ...
def getTop3Repositories(user: User): Seq[Repository] = ...
def getTop3Contributors(repo: Repository): Seq[Contributor] = ...

The main difference here is that all the functions return a collection monad. The getGithubUsers function now accepts a pattern and returns every github user whose name matches the pattern. The getTop3Repositories and getTop3Contributors functions return the top 3 repositories for a user and the top 3 contributors of a repository respectively.

Here’s what the monadic expression looks like.

for (
  user <- getGithubUsers("john.%")
  repository <- getTop3Repositories(user)
  biggestContributor <- getTop3Contributors(repository)
) yield s"$user/$repository: $biggestContributor"

This time, the expression is different, this is because we’ve renamed our functions. Other than the name of the functions and the argument to getGithubUsers(...) the monadic expression is the same as the previous ones. You should note that this expression returns a flat collection. In other words, it doesn’t return nested collections.

Recap

Before I finish, I want to quickly recap what we’ve seen.

First, we learned that monads are a tool for building little programs with a twist. The little programs are called monadic expressions. They’re built using a special syntax that makes use of monads. The monads are what provides the twist. They’re the ones that do the heavy lifting and that have to worry about the details of their respective behaviors.

We also learned some interesting properties and facts about monads:

  • Monads wrap other types.
  • Monadic expressions are composed of bindings and a return/yield statement.
  • Monadic expressions return monads.
  • The type of the monads used in bindings control which monad is used.
  • In a monadic expression, all the monads used in bindings must be the same type.
  • You can’t directly mix different types of monads, but there are less direct ways of doing so.
  • Every monad is different. You’ll need to look up their documentation before you can start using them.

Finally, we learned about a handful of different monads:

  • Option allows you to deal with values that can be empty. This is a great alternative to null values.
  • Either allows to specify two possible types for a value. This is great for error handling.
  • Eventually represents a value that will be available in the future. It helps you build asynchronous programs.
  • Collections are monads. They represent 0 or more values. They help you write programs that are run on multiple values.

Monads are here to make your life easier. They handle messy code and boilerplate for you. You don’t have to worry about missing values, errors, asynchronous code, or collections. The monads figure that out for you. They let you focus on the code that really matter and forget the boilerplate.

Thanks

I want to thank my pals Aaron, Frank and Isaac for reviewing the early drafts of this article and providing me with great feedback. Without them, I don’t think that this article would have turned into something that I can be proud of. Thanks guys!