What is a Monad?

You're probably already using monads but you may not realise it. If you've called flatMap on an Option or Future you're playing with monads. But what exactly are monads and why do we use them?

TL;DR - In very simple terms a Monad has a map and flatMap method and we use them for sequencing operations e.g. fetch a user based on her id, then fetch the orders associated with this user

What is a Monad?

A Monad is a Functor (has a map method) but it also has flatten and flatMap methods (flatMap is simply a combination of map and flatten).

Monads must obey certain laws. I don't believe it's necessary to fully understand these laws unless you're writing your own Monads so I won't go into them here. You can read more on the cats website. At this stage you just need to know that Future, Option, List and Either (Scala 2.12+) can all be thought of as Monads

What do we use Monads for?

We use the flatMap method to sequence effectful operations. In simple terms effectful means the operations return monads i.e. Future[String].flatMap(...): Future[A]

For comprehension is often used as syntactic sugar for flatMap calls:

def fetchUser(id: Int): Option[User] = ???  
def fetchOrder(user: User): Option[Order] = ???

// for comprehension is just map/flatmap
for {  
  user <- fetchUser(1)
  order <- fetchOrder(user)
} yield (user, order)

We can always compose Functors but not necessarily Monads

What does this mean? It means that so long as we just make use of the map method, we can write generic code that can handle any stack of Functors e.g. List[Option[String]], Option[Future[String]] etc. However the moment we start to use flatMap or flatten we can no longer handle any Monad stack generically.

To understand why this is the case we need to think about structure. In the case of map we're only changing the innermost type i.e. Future[Option[String]] => Future[Option[Int]] so the only code that needs to be specific is the code to handle this innermost type, maybe parseInt in this example. We don't need anything specific to Futures and Options (the Functors).

However with flatten or flatMap we're actually changing the structure of the Monad stack e.g. a flatten call would transform Seq[Option[String]] => Seq[String] so we need something which understands that a None should not be included in the List but a Some should. We call this code a Monad Transformer