Let’s say you are a typical scala programmer, making plenty of use of
Futures in your code. Sooner or later you end up having APIs like the following:
1 2 3 4 5 6 7
And for starters, let’s say you want to retrieve 3 articles and return something like
Future[Option((Article, Article, Article))] which implies that you want some tuple if you could retrieve all three articles, “none” tuple if any of the articles could not be found, and a failure if any of the database accesses failed.
A sample (and deliberately easy) implementation could look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13
The outer for comprehension will enter all the Futures in sequence (Monad!), return the value captured within the Future, which in our case are optional articles or “return” with a failed Future. The inner for comprehension will then look at all the optional values (returning none, when one is none) and yield the tuple, which is after exiting the inner for comprehension an optional triple. We return again from the outer for comprehension and the optional triple will be an Future[Option[(…)]].
In our case both,
Option are monads, and in the example the future is not really something we are interested in. It is just some technical detail of the API. It would be much nicer, if we could treat the
Future[Option[A]] construct as just a Option. And we can do this using
Monad Transformers. Here we use the
OptionT monad transformer. As is so often the case, a monad transformer will at first just wrap a value for us:
1 2 3 4 5
Before we discuss this, let’s see it in action:
1 2 3 4 5 6 7 8 9 10
So it saved a couple of lines, which could be condensed further, I just happened to add some type information of the intermediary results.
Let’s take the code apart. The apply function of OptionT just takes any F[Option[A]] (that we deal with F = Future can be derived by scalac) and returns an OptionT. It is now the job of the OptionT to transform the Future[Option[A]] into something akin to an Option. Hence the name: transform the outer monad in such a way, that it momentarily feels like an Option. So the return values in the for comprehension will now be
Option[Article]s as in the first try. We yield the triple, which results in an OptionT. To desugar the OptionT we simply call
run, which is the attribute of the OptionT monad transformer.
Cool. Let’s do that again.
Imagine we get a list of metainformation and we just want to get all the ids in a list. So the normal implementation would look like this:
1 2 3 4 5 6 7 8 9 10 11
And we can have the same procedure as every year, this time wrapping our
Future[List[A]] in a
1 2 3 4 5 6 7
So the principle is always the same. But Monad Transformers usually do more than just provide
flatMap constructs, so that they can be used in for comprehensions. They also provide access functions which are typical for the Monad we are transforming into. Some examples to show the principle:
1 2 3
Some practical hints:
- each time you have nested for comprehensions, you might be able to use Monad Transformers for clarity
- each time you deal with M[N[A]] constructs, where M and N are Monads you might be able to use Monad Transformers
- as with any Monad try to leave them as late as possible
- you can stack Monad Transformers, e.g. transform one transformer with another. This quickly leads to complex types and problems. Handle your tool with care
- many of the “simple” Monads in
scalazare implemented in terms of their Monad Transformers, e.g. State[S, A] is simply StateT[Id, S, A]
- ListT, OptionT and EitherT (for scalaz Either replacement \/) are easy to deal with
- instead of pattern matching, you can very often use
- check the docs and the companion objects - they contain plenty of useful transformer functions
Once you get used to the idea of a Monad Transformer, you can of course also exploit the fact that you can traverse and sequence over monads and to solve more complex problems.
For further articles in this series: TypeClass101