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.
Case 1
We have two entities : User
and 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).
1 2 |
|
But wait below function is not compiling…
1 2 3 4 5 6 |
|
It turns out that Future
and Option
monads do not compose in such a way.
For a first look, composition looks very natural in for
comprehension,
but if we transform it into series of flatMap
and 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
to return Option
in the middle and based on it return a next Future
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 Future
with Option
in a smart way to make
the composition possible.
Fusing Future
with Option
We already know that for
comprehension deals with flatMap
, map
,
withFilter
and foreach
. In our case compiler needs only flaMap
and map
to de sugar for
. So let’s introduce a new data type OptionFuture
,
which wraps Future[Option[A]]
and in a proper way handles
flatMap in order to compose Future
with Option
.
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
of Future
we are calling only two kinds of functions:
flatMap
Future.successful
It means that Future
can be swapped with Monad
.
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 Option
and
we call it OptionT
Monad transformer for Option
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
OptionT[F[_], A]
abstracts over F
and A
and it only requires that F
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 |
|
A solution
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.
Final word
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