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?
map
and flatMap
method and we use them for sequencing effectful operations e.g.
fetch a user based on her id; then fetch the orders associated with this userWhat is a Monad?
A Monad is a Functor (it 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] = ???
// <- is just a shortcut for 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
What next?
Lean about Monad transformers