How to compose Future and Option in Scala

January 23, 2015

Update (June 24, 2016) : New example using Hamsters lib


In Scala, it’s easy to compose options :

val oa: Option[String] = Some("a")
val ob: Option[String] = Some("b")

val oab : Option[String] = for{
  a <- oa
  b <- ob
} yield a+b

Note : you can read my helper about for comprehension translation to map/flatMap.

It’s also easy to compose futures, in a non blocking way :

def fa: Future[String] = Future("a")
def fb(a: String): Future[String] = Future(a+"b")

val fab : Future[String] = for{
  a <- fa
  ab <- fb(a)
} yield ab

But is it easy to compose several Future[Option[A]]? It’s a common problem if you call sequentially several external resources, that can give you one or zero value.

def foa: Future[Option[String]] = Future(Some("a"))
def fob(a: String): Future[Option[String]] = Future(Some(a+"b"))

/* can't compose like this : type mismatch
val composedAB = for {
  optA <-foa
  a <- optA // option instead of Future
  ab <- fob(a)
}yield ab
*/

We can’t compose easily fob and foa: types will mismatch because futures and options don’t compose naturally.

Fortunately, there are some solutions.

Handmade binding

You can solve this with pattern matching :

val composedAB: Future[Option[String]] = foa.flatMap {
  case Some(a) => fob(a) //(*)
  case None => Future.successful(None)
}

(*) : In the for, call fob with the value inside the option and get a Future[Option[String]]. Doing this, you can compose result from foa and fob and keep consistent types.

Using a custom FutureO monad

This is an idea from Edofic’s blog. It allows more natural composition of Option and Future types.

You can create a custom FutureO monad that will compose well with other FutureO instances.

You just need to define a constructor and flatMap (plus map as it’s used by for comprehension syntactic sugar) :

case class FutureO[+A](future: Future[Option[A]]) extends AnyVal {
  def flatMap[B](f: A => FutureO[B])(implicit ec: ExecutionContext): FutureO[B] = {
    val newFuture = future.flatMap{
      case Some(a) => f(a).future
      case None => Future.successful(None)
    }
    FutureO(newFuture)
  }

  def map[B](f: A => B)(implicit ec: ExecutionContext): FutureO[B] = {
    FutureO(future.map(option => option map f))
  }
}

Then you can use a simple for comprehension form :

val composedAB2: Future[Option[String]] = (for {
  a <- FutureO(foa)
  ab <- FutureO(fob(a))
}yield ab).future

Or, with flatMap :

val noSugarComposedAB2 = FutureO(foa).flatMap(a => FutureO(fob(a))).future

Using a Scalaz monad transformer

Finally, if you want to generalize this kind of usage, you should try Scalaz monad transformers, which work for a larger kind of types than options and futures.

import scalaz._
import Scalaz._
import scalaz.OptionT._

// We are importing a scalaz.Monad[scala.concurrent.Future] implicit transformer

val composedAB3: Future[Option[String]] = (for {
  a <- optionT(foa)
  ab <- optionT(fob(a))
}yield ab).run

Update : Using Hamsters monad transformers

Since the moment I wrote this post, I’ve made a small library that provides (among other things) some simple monad transformers :

val composedAB: Future[Option[String]] = for {
  a <- FutureOption(foa)
  ab <- FutureOption(fob(a))
} yield ab

You can find more information about Hamsters here.

Discussion, links, and tweets

comments powered by Disqus