Monad transformers allow us to stack monads. Say we have a monad, like Option
, and we want to wrap it in another monad, like \/
, in a convenient way (where convenient is to be defined shortly). Monad transformers let us do this. Scalaz comes with lots of monad transformers. Let’s see how to use them and the benefits they supply.
Example
Let’s motivate monad transformers with the above example. We have an operation that may or may not generate a value. Something like getting a value from a database. Thus we model it as an Option
. This operation may also encounter various other errors. To keep things simple we’ll encode the errors as String
s. So we basically have three different types of result:
- value found;
- value not found; or
- error occurred.
We can model this as type Result[+A] = String \/ Option[A]
. (Note that \/
is Scalaz’s version of Either
.) We can construct values of this type by hand:
To use result
we must unwrap it twice, which is tedious:
This is fairly horrible. The desugared version is actually a bit clearer:
Still, can’t we avoid this nesting? It seems like we should be able to chain calls to flatMap
and friends and just have it do the right thing. With monad transformers we can. Here’s the transformed version.
There are three changes:
- the type definitions;
- the way we construct values of these new types; and
- the removal of one layer of nesting when we use the monad.
Let’s go through these in order.
Type Definitions
The type OptionT[M[_], A]
is a monad transformer that constructs an Option[A]
inside the monad M
. So the first important point is the monad transformers are built from the inside out.
Note that I define a type alias Error
for the monad we wrap around the Option
. Why is this the case? It has to do with type inference. OptionT
expects M
to have a type (technically, a kind) like M[A]
. This is, M
should have a single type parameter. \/
has two type parameters, the left and the right types. We have to tell Scala has to get from two type parameters to one. One option is to use a type lambda:
Clearly this horror should not be inflicted on the world. A saner option is to just define the type as I have done above with Error
.
Constructing Values
Constructing values of Result
can be done in a variety of ways, depending on what we want to achieve.
If we want to construct a value in the default way, we can use the point
method like so:
Note that this wraps our value in Some
and a right \/
.
What if we want, say, a None
for the option. We can’t use point
as we have above:
The solution is to use the OptionT
constructor:
Here I’ve used Scalaz’s none
, and point
on Error
to construct the value of the correct type, before wrapping it in an OptionT
.
If we want to create a left \/
we go about it the same way:
Note the type declaration, needed to assist type inference.
Using the Monad
When we use our new monad map
and flatMap
do multiple levels of unwrapping for us.
Of course we might not want to unwrap all the layers of our monad. We can manually unwrap our data if need be. All monad transformers in Scalaz return their data if you call the run
method. With this we can do whatever we want, such as folding over the \/
.
What about some utility functions to help with this? There are no such methods defined for all monad transformers, that I know of, but in the particular case of OptionT
we can use the flatMapF
method. For a type OptionT[F[_], A]
the normal flatMap
has type
whereas flatMapF
has type
(Note I removed an implicit parameter from the method signatures above.)
For our Result[Int]
type this means the parameter f
to flatMapF
should have type Int => \/[String, B]
. Note that B
is not wrapped in an Option
; flatMapF
will do this for us. We also don’t have to wrap our result in OptionT
.
Here is an example:
Conclusion
Once you start using monads it’s quite easy to find yourself using a lot of them. For example, if you do asynchronous programming in Scala you are likely using Future
which is a monad1. Debugging futures can be hard. Futures don’t give good stack traces due to the continual context switches, and logs are hard to interpret as output from many threads is mixed together. An alternative is to keep an in-memory log with each computation running in a Future
, and output this log when the Future
finishes. This can be accomplished using the Writer
monad. As soon as you do this you have a stack of monads, and monad transformers become useful. Once you know what to look for you’ll find them everywhere!
-
See the scalaz-contrib package for
Monad
instances forscala.concurrent.Future
. ↩