Every blogger that is remotely interested in functional programming seems to have to write an article that tries to explain monads at some point. So I will get this over with right with my first real post :-) I will try to approach the topic with some trivial examples. The code samples are in Scala. Let’s go.

Only unary type constructors can be monads

In the course of this article, I am using three examples for monads: the List monad, the Maybe monad (aka Option aka Optional), and Future (aka Task). Based on these examples, let’s see what monads are and what monads are not.

Say, we have a value l that is a list of strings. In Scala:

val l: List[String]
  • l is a value and values can not be monads

  • List[String] is a type and types can not be monads

  • List[_] is a type constructor and actually a monad

What does that mean? It means that all monad-related code is defined on the level of type constructors. Type constructors yield a concrete type when given a type parameter. In Scala, a type constructor with one type parameter A may look like this:

  • List[A]

  • Option[A]

  • Future[A]

Only a type constructor that takes one type parameter can be a monad. For type constructors with more parameters (such as Either[E, A]), one of the type parameters can be chosen as the monad’s type parameter, for example by defining a type alias.

Monadic values are the result of a computation

Now that we know what a monad looks like, we can take a look at where they occur. I think the easiest way to to get an intuitive understanding of monads is to view them as the result type of a computation. In other words, a monadic value results from a function that does some computation:

  • a value of the type List[A] results from a computation that yields several values of the type A. For example, a List[String] could be returned by a function that obtains all words in a given sentence.

  • a value of the type Option[A] results from a computation that may fail or yield a value of the type A. For example, an Option[Int] could be returned by a function that tries to parse an integer from a string.

  • a value of the type Future[A] results from a computation that might take some time to yield a value of the type A. For example, a Future[Boolean] couzld be returned by a function that determines whether a given integer is a prime number.

For a monadic type M[A], the nature of the computation is described in M, whereas the type of the value that the computation produces is A.

Monadic values can be chained

OK, type constructors can represent computations and type constructors can be monads. But what makes a type constructor a monad? And why are monads useful in the context of computations? The answer to both questions are the two functions that are defined for monads. Both functions’ implementations are specific for each monad.

The first function is called pure, point, or return. It takes a value a of the type A and executes a default computation that yields a:

  • for List[A], pure(a) produces the singleton list List(a)

  • for Option[A], pure(a) produces a success Some(a)

  • for Future[A], pure(a) produces a future that immediately finishes and yield a

The second function is called bind or flatMap. It takes a value ma of the type M[A] and a function f of the type A => M[B] and applies f to the result yielded by ma.

  • for List[A], flatMap(ma, f) applies f to every list element in ma and returns all resulting values in a list.

  • for Option[A], flatMap(ma, f) returns None if ma is None, and returns the result of applying f to the contents of ma if ma contains a value.

  • for Future[A], flatMap(ma, f) returns a future that contains the result of applying f to the result of ma.

flatMap is the function that makes monads so useful. It allows chaining multiple function calls that do computations to an overall function that does a combined computation. Future is maybe the most obvious example: if you have functions f1: A => Future[B], f2: B => Future[C], and f3: C => Future[D], you can chain them easily to a new function:

def f123: A => Future[D] = (a: A) => f1(a).flatMap(f2).flatMap(f3)

That’s it for the nature of monads. I hope this post conveys an intuition of why they are useful: chaining computations. The technical details, such as Scala’s for-comprehension, the support for monads in libraries such as Cats and Scalaz, or how monads appear in other programming languages, are left for other posts.