Scala Future[Option[T]] 解包

Scala Future[Option[T]] Un Packing

在以下代码段中,

trait MyType1; trait MyType2
import scala.concurrent.Promise

val p1 = Promise[Option[MyType1]]()
val p2 = Promise[MyType2]()

我将 p1 和 p2 传递给另一个函数,在该函数中我使用成功的 Future 完成了 Promise。调用此函数后,我尝试读取 Promise 中的值:

trait Test {
  // get the Future from the promise
  val f1 = p1.future
  val f2 = p2.future

  for {
    someF1Elem <- f1
    f1Elem     <- someF1Elem
    f2Elem     <- f1Elem
  } yield {
    // do something with f1Elem and f2Elem
    "..."
  }
}

当我尝试编译它时,我遇到了一些编译器问题。

Error:(52, 19) type mismatch;
 found   : Option[Nothing]
 required: scala.concurrent.Future[?]
      flElem     <- someF1Elem
                  ^

IntelliJ 没有显示任何错误或任何错误,类型看起来一致。但我不确定为什么编译器不开心!有什么线索吗?

在 for 理解中,<- 右侧的表达式必须具有兼容的类型。这是因为 for 理解基本上是嵌套 flatMap 调用的语法糖。要解决这个问题,您可以将一个 for comprehension 嵌套在另一个 for comprehension 中,或者您可以在 Option 上使用 getmap 等方法。

另一种选择是使用 monad transformers,但这超出了本答案的范围。

你的 for comprehension 类型必须一致,所以你不能随意混合使用 OptionFuture

在您的示例中,f1 returns 一个 Future[Option[MyType1]]f2 returns 一个 Future[MyType2]

请记住,对于一系列 flatMap/map 和潜在 withFilter.

的理解脱糖

Future[A]Option[A]flatMap 的(简化)签名也是

def flatMap[B](f: A => Future[B]): Future[B]
def flatMap[B](f: A => Option[B]): Option[B]

for-comprehension脱糖的前两步

f1.flatMap { someF1Elem =>
  someF1Elem.flatMap { f1Elem => // this returns a `Option[MyType1]`
     ...
  }
}

现在看到错误了吗?


现在,要解决此问题,您可以采用几种方法。一个非常方便的方法是使用 Monad Transformers,它允许您将(例如)FutureOption 组合成一个单一的 monad 类型 OptionT.

具体可以从Future[Option[A]]OptionT[Future, A]来回切换。基本思想是您可以在后者上进行 flatMap 并提取 A.

类型的值

但在此之前,您需要先创建 "right shape" 类型,这样两者都是 Future[Option[Something]]

这是一个使用 scalaz 的例子

import scalaz._; import Scalaz._ ; import scalaz.OptionT._
import scala.concurrent.{ Promise, Future }
import scala.concurrent.ExecutionContext.Implicits.global

trait MyType1
trait MyType2

val p1 = Promise[Option[MyType1]]()
val p2 = Promise[MyType2]()

val f1 = p1.future
val f2 = p2.future 

val res = for {
  f1Elem <- optionT(f1)
  f2Elem <- f2.liftM[OptionT]
} yield {
  println(s"$f1Elem $f2Elem")
}

optionT 在给定 Future[Option[A]] 的情况下构建 OptionT[Future, A],而 liftM[OptionT]Future[A]

时实现相同的效果

本例中res的类型是OptionT[Future, Unit],调用run可以得到Future[Option[Unit]]

"How does yield work",你的理解力相当于

f1.flatMap(someF1Elem: Option[MyType1] => 
  someF1Elem.flatMap(f1Elem => 
    f1Elem.map(f2Elem =>
      "..."
    )
  )
)

基本上发生的事情是这样的:Future 上的 flatMap 被定义为采用 returns 另一个 Future 的函数。 这给你一个类似的错误:

<console>:64: error: value map is not a member of MyType1 f1Elem.map(f2Elem => ^ <console>:63: error: type mismatch; found : Option[Nothing] required: scala.concurrent.Future[?] someF1Elem.flatMap(f1Elem => ^

如果您希望操作异步执行,即真正映射 Future 个实例,则必须返回到 Future[Option[X]]。正如 Kim 所建议的,您可以嵌套理解:

import scala.concurrent.{Future, Promise}

def test: Future[Option[String]] = {
  val p1 = Promise[Option[MyType1]]()
  val p2 = Promise[MyType2]()
  // ... run code that completes the promises

  val f1 = p1.future
  val f2 = p2.future

  for {
    someF1Elem: Option[MyType1] <- f1
    f2Elem     <- f2
  } yield {
    for (f1Elem <- someF1Elem) yield "something"
  }
}

您还可以在未定义选项 (NoSuchElementException)

时使生成的 Future 失败
def test: Future[String] = {
  val p1 = Promise[Option[MyType1]]()
  val p2 = Promise[MyType2]()
  // ... run code that completes the promises

  val f1 = p1.future
  val f2 = p2.future

  for {
    someF1Elem: Option[MyType1] <- f1
    f2Elem <- f2
  } yield {
    val f1Elem = someF1Elem.get  // may cause an exception and fail the future
    "something"
  }
}