Practical Future[Option[A]] in Scala
Motivation
In real world concurrent code you often come across the Future[Option[A]]
type(where A
is usually some concrete type). And then you need to compose these things. This is not straightforward in Scala.
If you’ve done some Haskell just import scalaz._
and you can skip the rest of this article. Scalaz library defines a monad typeclass(and many others) that formally specifies what it means to be a monad(not “has a flatMap-ish thingy”). Then it’s easy to build abstractions upon this.
But what if you don’t want to add another dependency and just want to make this tiny bit more practical? I can be done and is not that complicated. You will also learn something and maybe even become motivated to bite the bullet and start using Scalaz.
Meaning
The Future[Option[A]]
type combines the notion of concurrency(from Future
) and the notion of failure(from Option
) giving you a concurrent computation that might fail. What we want is a monad instance for this. With only standard library code you probably would use Future#flatMap
in combination with Option#map
and Option#getOrElse
(or just Option#fold
). This gets messy and unreadable quite quickly due to loads of boilerplate. Let’s fix this!
Newtyping
A monad in scala means a flatMap
method, so it’s safe to assume we need to define a flatMap
with this special semantics somewhere.
You might want to define an implicit class that has the new method but it wouldn’t work as Future
already has a flatMap
method.
I took a page from Haskell’s book. When you want to override semantics there you wrap up the type into a newtype. This is just compile-time type information that is completely free at runtime. Luckily Scala has this in form of AnyVal
.
|
|
I’ve also made it covariant for ease of use.
The Monad
It’s actually quite easy to implement
import concurrent.{Future, ExecutionContext}
|
|
You need to pull in an execution context and then do the usual boilerplate thing ending up with a wrap again. I’ve also added map
method which is trivial but we need it because in Scala for comprehensions are desugared into flatMap
and map
to avoid using point
(abstract constructor) since it’s harder to express OO-style..
Usage
What good is this FutureO
? Let’s do a contrived example. We need a function that might fail - divideEven
that only divides even numbers. And we need concurrency - we’ll divide two numbers concurrently.
|
|
It works. How would this look without FutureO
?
|
|
Two layers of for comprehensions. It gets even hairier if you have data dependencies between your futures. Consider divideEven
again but this time we want to divide a number twice in a row. And we’ll be keeping the futures around just to prove a point. Let’s imagine that divideEven
does some blocking IO and we want to push it into another tread-pool.
|
|
And it works as expected. The FutureO
part there is just to alter the monadic semantics. As an exercise try to rewrite this without FutureO
and squirm in disgust.
Usability
Inside for comprehension(or manual flatMaps) you can still construct failed futures, throw or return None
(in a future). However putting in a default value(getOrElse) is a bit trickier and going back to regular Future
inside same comprehension is impossible. But you can fix this. You can define methods like orElse
on the FutureO
. You can also overload the flatMap
to enable interop with regular futures. However this screws up type inference and I would advise against it as it could introduce some nasty bugs.
Try to implement combinators you need and leave some comments. Especially if you come across something nice or find a case where FutureO
is more awkward to use than regular futures.
Theory
What we defined is actually a specialized monad transformer. Monads(such as Future
and Option
) have this nasty property that they don’t compose. You cannot write a function that takes a two monads and outputs a composed one.
However you can write such a function if you fix one of the monads and only take one as a parameter. This is called a monad transformer. In this case I fixed Option
. Take a closer look, we are only using flatMap
and a constructor(point
) from Future
. This means we could abstract over the whole monad class. And this is what Scalaz does with OptionT
. But to do this it needs to define a monad typeclass and instances for each and every monad they find.
What I did is fix the other monad too and this produces a concrete instance you can use without any typeclasses. There is a downside of course. This is a one-off hack. If you want other transformers you’ll have to write them as well. At that point I think would be a good idea to start using Scalaz.
Last modified on 2014-03-07
Previous Comparing Haskell Web FrameworksNext Coroutines for free