What is a monad
Monad is a trait with the following operations:
Having such trait implementation for a type M
makes it a monad instance.
Sometimes, pure
is called return
or point
, whereas bind
might instead be named flatMap
(preferred in Scala) or >>=
(Haskell's way). The method names are not so important, what's important is their signatures and what they do.
pure
and bind
implementations must obey some easily google-able mathematical laws omitted here to make things less boring.
M
type parameter
Type parameter of higher kind M
requires a monad instance to be parameterized by a type with a single type parameter, e.g. List
, Future
, Option
, ({ type λ[A] = Either[Throwable, A] })#λ
, or any type similar to:
pure
operation
In a nutshell, pure
gives a way to wrap a value of type A
into type M
(sometimes called monadic context). E.g.: List(a)
, Future { a }
, Some(a)
, Right[Throwable, A](a)
. Put clearly: pure
is A => M[A]
, and that's it.
bind
operation
The essence of this operation is to allow flattening of M[M[A]]
to M[A]
. This might be not clear immediately, given the above Monad
trait definition, but that's easy to understand: having a value ma: M[A]
and a function f: A => M[B]
, to apply this function to ma
, one needs a way to apply f
to an a: A
that's inside the context M, and the result will be of type M[M[B]]
. So, bind
allows to apply a function to monadic values and to flatten nested monadic contexts, while pure
- to add new ones. For example, List(a).flatMap(a => List(a))
, Future { a } flatMap { a => Future { a } }
, Some(a).flatMap(a => Some(a))
. These are artificial examples, but it's easy to imagine some nested conditional logic in the supplied f
function.
Different perspective on Monad
trait
It turns out, Monad
trait might be formulated in a different way that is closer to the flattening intuition above:
Here, join
(or flatten
) does exactly that. Also, we've got a map
operation which is needed to fill the 'gap' from loosing the full power of bind
. Those two definitions are equivalent: bind
can be implemented in terms of join
and map
:
or map
and join
in terms of bind
and pure
:
Different perspective on map
and bind
There is also an alternative view on map
and bind
methods signatures. Let's call 'em lift
and liftM
accordingly:
Notice how similar they are. Both take a function, either ordinary A => B
in case of lift
or a monadic one A => M[B]
(sometimes called Kleisli arrow) in case of liftM
, and lift it to a function that works on monadic values. And these definitions are easily expressible in terms of original map
/bind
:
So, from this perspective, a monad can also be defined using pure
and liftM
or pure
, join
, and lift
.
The essence of monads
From the dry definition above it might be not clear how monads can be useful, why they exist in the first place. So, to put it simply, monads give a way to chain computations (of type A => M[B]
) while abstracting some details of how to exactly do that (extract values out of monadic context and flatten nested contexts) into the monad itself. For example, for Lists
s it performs flattening, for Option
s it stops on first None
, for Future
s - allows to make another asynchronous computation from within asynchronous computation, for some more esoteric state monad - to emulate mutable state in pure functional setting. This is what actually happens in Scala when using for comprehension syntax (though it doesn't force to implement any kind of trait, like the Monad
above: just follow the method signatures, and all' be fine):
And it's just a syntax sugar for:
Unlike the Monad
trait definition above, monadic operations here are written in object-oriented style, but that doesn't change their meaning.
Some monad instances
A monad's essence is in how chaining (bind
operation) is implemented. So, here some monads instances to make it all clearer:
Notice how partial type application is used for Either
which has two type parameters while monad requires just one. In general, for types having more than one type parameter, one needs to fix all the parameters but one to make an, in fact, a new type that could potentially be a monad instance.
Conclusion
Don't expect to understand the nature of monads just in one seat if you are new to them: you definitely need to play with various monads, see how they behave, implement some of them on your own. But the underlying idea is simple and is just a common programming practice: separate concerns and, as a consequence, prevent code duplication, by moving common stuff into a single easily maintainable and comprehensible place.