Search code examples
scalafor-comprehension

Future[Option[Boolean]] in a for comprehension.. Simple right?


Suppose I have:

val res:Future[Option[Boolean]] = Future(Some(true))

and I can do:

res.map(opt => opt.map(r => print(!r)))

I guess the for comprehension for this would be:

for {
   opt <- res
   r <- opt
} yield (print(!r))

but this does not work! I get an error ie:

error: type mismatch;
found   : Option[Unit]
required: scala.concurrent.Future[?]
r <- opt

How do I use a Future[Option[Boolean]] in a for comprehension to extract or convert the Boolean?

Note: this is a simplification of the problem I have at the moment with many Future[Option[Boolean]] variables that I would like to use together in a for comprehension.


Solution

  • A for-comprehension really makes this look like it should all work, doesn't it? But let's think about what you're asking for.

    First, note that for un-nests:

    for {xs <- List(List(5)); x <- xs} yield x
    

    produces

    List(5)
    

    Now, without even getting into the type signatures or desugaring, we can think about replacing List with some arbitrary type T, and we'll call the contained type A:

    for { xs <- T(T(a: A)); x <- xs } yield x
    

    and we should get a

    T[A]
    

    back (presumably the one we put in, but the types don't actually promise us that).

    Okay, but what about

    for { xs <- T(U(a: A)); x <- xs } yield x
    

    ? This is strictly more general than the case where the two things have the same nesting. Well, if T and U both have a common supertype S, then we can just view the whole thing as S(S(a: A)), so we'll at least get an S back. But what about in the general case?

    The bottom line is that it depends. For example, let's consider the case where T=Future, U=Option. We have the following possibilities:

    Success(Some(a))
    Success(None)
    Failure(t: Throwable)
    

    Now, can we come up with any coherent policy for unwrapping? If we unwrap into a Future, then what A do you use for the Success(None) case? You don't have one available to return. Likewise, if you try to vanquish the outer Future, how do you know, without explicitly stating it somehow to the compiler, that Failure should be mapped to None (if indeed it should--maybe it should go to a default!).

    So the bottom line is that you just can't do this correctly in general without specifying what is supposed to happen for every pair T[U[_]]. (I encourage the interested reader to peruse tutorials on monads and monad transformers.)

    There is a way out, though: if you can explicitly turn your U into a T, or your T into your U, you can take advantage of the unwrapping capability. It's pretty easy to turn an Option into a Future , so the easiest solution is

    for { opt <- res; r <- Future(opt.get) } yield r
    

    (where just let the exception get thrown on none.get). Alternatively, you can turn the Future into an Option with the slightly ugly

    for { opt <- res.value.flatMap(_.toOption); r <- opt } yield r