Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`Future[Option[Future[Option[X]]]]` into `Future[Option[X]]`

Tags:

scala

How to transform Future[Option[Future[Option[X]]]] into Future[Option[X]]?

If it were a TraversableOnce instead of Option I'd use the Future companion object; but what about Options?

Example:

def processAndReturnFuture(x:String):Future[String] = future(x)
def processAgainAndReturnOption(x:String):Option[String] = Some(x)

val futOpt:Future[Option[String]] = future(Some("x"))
val futOptFutOpt:Future[Option[Future[Option[String]]]] =
  futOpt.map( opt =>
    opt.map( x =>
      processAndReturnFuture(x).map( processedX =>
        processAgainAndReturnOption(processedX)
      )
    )
  )
like image 489
Dominykas Mostauskis Avatar asked Oct 18 '13 11:10

Dominykas Mostauskis


4 Answers

Updated answer

This might do the trick. What I did was to replace your first two map calls with a flatMap on the outermost Future and a pattern match on the outermost Option.

val futOptFutOpt: Future[Option[String]] =
  futOpt.flatMap {
    case None => Future.successful(None)
    case Some(x) =>
      processAndReturnFuture(x).map {
        processedX => processAgainAndReturnOption(processedX)
      }
  }

Initial answer

I presume somewhere in your code you have a map call that transforms a Future[Option[A]] into a Future[Option[Future[Option[X]]]]. Replace that map with a flatMap and drop the top-most Option layer in the result. You'll end up with Future[Option[X]]. Here's what I mean:

scala> import scala.concurrent._
import scala.concurrent._

scala> import ExecutionContext.Implicits.global
import ExecutionContext.Implicits.global

scala> val f1: Future[Option[Future[Option[String]]]] =
     | Future.successful(Some(1)).map(v => Some(Future.successful(Some(v.toString))))
f1: scala.concurrent.Future[Option[scala.concurrent.Future[Option[String]]]] = scala.concurrent.impl.Promise$DefaultPromise@6f900132

scala> val f2: Future[Option[String]] =
     | Future.successful(Some(1)).flatMap(v => Future.successful(Some(v.toString)))
f2: scala.concurrent.Future[Option[String]] = scala.concurrent.impl.Promise$DefaultPromise@2fac9a62

I'm probably not entirely correct as to what your actual context is, but you'll probably solve this by replacing a few maps with a few flatMaps.

like image 182
Ionuț G. Stan Avatar answered Oct 19 '22 16:10

Ionuț G. Stan


If you are using scalaZ or cats, you can use more generic approach, which would work for more types ( Future would require to have Traverse typeclass, and Option just need to be a Monad)

First way is:

  import scalaz.std.scalaFuture.futureInstance
  import scalaz.std.option.optionInstance
  import scalaz.syntax.traverse._

  val fofo: Future[Option[Future[Option[Int]]]] = Future(Option(Future(Option(5))))
  val ffoo: Future[Future[Option[Option[Int]]]] = fofo.map(_.sequence)
  val fo = fofo.map(_.sequence).join.map(_.join) // Future(Some(5))

Here, you first "swap" types inside future with sequence, and then join them together. So for any two higher kinded types, F[_] and G[_], where F - has a Traverse instance and G is a Monad - you can do that. Change the Future to List, and implementation would not change.

Another way to do it, which might be interesting - is to use monad transformer. Idea is, that in Monad you can join types together F[F[A]] => F[A].

Future(Future(4)).join // Future(4)
Option(Option(3)).join // Option(3)
List(List(1, 2)).join  // List(1, 2)

If you think of Future[Option] as an F type in this context - you can join them together! All you need is just to show that Future[Option] is a monad. ScalaZ has a monad transformer for that (the way to "compose" or assemble bigger Monads from the smaller ones).

import scalaz.OptionT.optionT

  val FOT = OptionT.optionTMonadPlus[Future] //creating monad transformer on top of Option
  val fo2 = FOT.join(optionT(fofo.map(_.map(optionT(_))))).run  // Future(Some(5))

optionT puts a Future[Option] inside a monad, so we need to do it 2 times - one for outer Future[Option] and one for inner Future[Option]

This approach would work for any types F[G[F[G]], for which you have a Monad Transformer.

We can also make it a little bit nicer and avoid this two ugly map inside Future. Map comes from Functor typeclass and they are composable! So we can assemble our new Functor[Future[Option]] and map over it:

val FOT = OptionT.optionTMonadPlus[Future]
val FOF = Functor[Future].compose[Option]
val fo2 = FOT.join(optionT(FOF.map(fofo)(optionT(_)))).run
like image 39
I See Voices Avatar answered Oct 19 '22 14:10

I See Voices


Here's my take on the matter:

val futOptFutOpt: Future[Option[String]] =
  futOpt.map(_.toSeq)
    .flatMap(Future.traverse(_)(processAndReturnFuture))
    .map(_.headOption)

What this basically does is

  1. Convert futOpt from a Future[Option] into a Future[Seq]
  2. Process each element of the Seq with processAndReturnFuture and return a new Future[Seq]
  3. Convert Future[Seq] back into a Future[Option]
like image 1
Zoltán Avatar answered Oct 19 '22 14:10

Zoltán


Not exactly an answer but a recommendation: try to avoid composing recursively, instead use for-yield

def someFunc(fo1: Future[Option[String]], fo2: Future[Option[String]]): Future[Option[String]] ={
    //this is what we want to avoid
    //val fofo1:Future[Option[Future[Option[String]]]] = fo1.map(o => o.map(s => fo2))

    //instead use for
    val res : Future[Option[String]] = for {
        o1 <- fo1
        o2 <- fo2
    }yield{
        println(o1 + "do what ever you want" + o2)
        //or use for a second time
        for{
            s1 <- o1
            s2 <- o2
        }yield{
            s"$o1, $o2"
        }
    }
    res
} 
like image 1
ozma Avatar answered Oct 19 '22 14:10

ozma