`未来[选项[未来[选项[X]]]]`进入`未来[选项[X]]`

时间:2013-10-18 11:38:45

标签: scala

如何将Future[Option[Future[Option[X]]]]转换为Future[Option[X]]

如果是TraversableOnce而不是Option,我会使用Future companion object;但是选项呢?

示例:

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)
      )
    )
  )

4 个答案:

答案 0 :(得分:7)

更新回答

这可能会成功。我所做的是用最外面的map上的flatMap和最外面的Future上的模式匹配替换前两个Option来电。

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

初步答复

我假设您的代码中某处有一个map调用,可以将Future[Option[A]]转换为Future[Option[Future[Option[X]]]]。将map替换为flatMap,并删除结果中最顶层的Option图层。你最终会得到Future[Option[X]]。这就是我的意思:

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

关于你的实际情境,我可能并不完全正确,但你可能会用一些map替换一些flatMap来解决这个问题。

答案 1 :(得分:4)

如果您使用的是scalaZcats,则可以使用更通用的方法,这种方法适用于更多类型(Future需要Traverse类型类,以及Option只需要成为Monad

第一种方式是:

  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))

在这里,你首先"交换"使用sequence将来的类型,然后将它们连接在一起。 因此,对于任何两种更高级的类型,F[_]G[_],其中F - Traverse实例,GMonad - 您可以做到这一点。 将Future更改为List,实施不会改变。

另一种可能有趣的方法是使用monad变换器。 想法是,在Monad中,您可以将类型加在一起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)

如果您在此上下文中将Future[Option]视为F类型,则可以将它们组合在一起!您只需要表明Future [Option]是一个monad。 ScalaZ有一个monad变换器(通过"组合"或者从较小的Monad组装更大的Monad)。

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))

optionTFuture[Option]放入monad中,因此我们需要执行2次 - 一次用于外部Future[Option],另一次用于内部Future[Option]

这种方法适用于任何类型的F [G [F [G]],你有一个Monad变压器。

我们也可以让它更好一点,并避免在map内这两个丑陋的Future。地图来自Functor类型类,它们是可组合的!因此,我们可以将新的Functor[Future[Option]]map组合在一起:

val FOT = OptionT.optionTMonadPlus[Future]
val FOF = Functor[Future].compose[Option]
val fo2 = FOT.join(optionT(FOF.map(fofo)(optionT(_)))).run

答案 2 :(得分:1)

以下是我对此事的看法:

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

这基本上是做什么的

  1. futOptFuture[Option]转换为Future[Seq]
  2. 使用Seq处理processAndReturnFuture的每个元素并返回新的Future[Seq]
  3. Future[Seq]转换回Future[Option]

答案 3 :(得分:1)

不完全是答案而是建议:尝试避免递归编写,而是使用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
}