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)
)
)
)
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)
}
}
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 map
s with a few flatMap
s.
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
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
futOpt
from a Future[Option]
into a Future[Seq]
Seq
with processAndReturnFuture
and return a new Future[Seq]
Future[Seq]
back into a Future[Option]
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With