Someone have said that monads are like burrito, if you ever taste one than you can’t imagine live without it.
Monads are a powerful tool. Thanks to them we can abstract over computation. We can make one computation depended on another and if needed fail fast.
But one day the time will come when we have two different monads and we will find out that they don’t compose !
Let’s make some code to visualize the problem. I am going to show two use cases and I will start with the simplest one.
We have two entities :
Address and two functions retrieving data
with the respect of a given predicate
1 2 3 4 5
Our goal is to write a function which for a given login returns user’s street name
1 2 3 4 5
So far so good - quite simple and classic enterprise task :)
However there are two caveats to this solution worth noting. What happened
if there is no such user or the user exists but it has no address ?
It is obvious that we will see
Null Pointer Exception - sick !
Of course we can filter out those nulls and rewrite functions to be aware of
them but as you already know this is also not a good solution. Can we
do better ? Yes we can, let’s introduce a context aware
of whether value exists or not (
Option data type).
But wait below function is not compiling…
1 2 3 4 5 6
It turns out that
Option monads do not compose in such a way.
For a first look, composition looks very natural in
but if we transform it into series of
map at the end, we
will notice that the puzzles don’t feet. If we start with
Future than the
function passed to
flatMap must return a
Future. In our case we want
Option in the middle and based on it return a next
being a container fo an user’s possible address.
Equipped with this knowledge we can rewrite our function in the following way
1 2 3 4 5
Now it compiles and return correct results. But it is not as readable as our first naive attempt. Can we do better ? Ideally we would want to have something like
1 2 3 4 5
We need somehow to fuse
Option in a smart way to make
the composition possible.
We already know that
for comprehension deals with
foreach. In our case compiler needs only
to de sugar
for. So let’s introduce a new data type
Future[Option[A]] and in a proper way handles
flatMap in order to compose
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Take a little time to better look at
OptionFuture data type.
First question coming to my mind is - can we make it more abstract ?
It turns out that we can abstract over
Future very easly. In terms
Future we are calling only two kinds of functions:
It means that
Future can be swapped with
What about the
Option ? Over the
Option we are performing pattern matching
- so it means that we need to know something about it structure.
And because of that we can’t to abstract over it.
This leads us to the definition of monad transformer for
we call it
Monad transformer for
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
OptionT[F[_], A] abstracts over
A and it only requires that
is a monad. The name of the monad transformer comes from the fact that,
in order to implement this wrapper, we need to know what the inner
most monad in the stack is - in this case
Option. Without this knowledge
we can’t compose any two given monads with itself.
Monad quick recap
A minimal api for monad can be described by following trait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
And its instance for
Future you can find below.
1 2 3 4 5 6 7 8 9
Putting all pieces together we can finally write
1 2 3 4 5 6 7 8
of course we can return directly
Future[Option[String]] just by calling
value function on the result like
1 2 3 4 5
` and that’s it.
At the beginning I said that I have two cases to show, but because the post could be to long to go through without a brake I decided to split it into two pieces. The whole code base used in this post can be found in the following link
More in part 2