Scala Future [选项[T]] Un Packing

时间:2015-07-20 13:33:09

标签: scala promise future for-comprehension

在以下代码段中,

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没有显示任何错误,也没有显示任何错误,并且类型看起来是对齐的。但是我不确定编译器为什么不开心!有线索吗?

3 个答案:

答案 0 :(得分:15)

您的理解类型必须一致,因此您无法以您的方式自由地混合OptionFuture

在您的示例中,f1返回Future[Option[MyType1]]f2返回Future[MyType2]

请记住,对于一系列flatMap / map以及可能withFilter的一系列理解是荒谬的。

flatMapFuture[A] Option[A]的(简化)签名

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

for-comprehension desugar to the

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

现在看错了吗?

现在,为了解决这个问题,您可以遵循一些方法。一个非常方便的是使用Monad Transformers,它允许您将(例如)FutureOption组合成单个monad类型OptionT

具体来说,您可以从Future[Option[A]]来回OptionT[Future, A]。基本思想是你可以对后者进行flatMap并提取A类型的值。

但在此之前,您需要制作“正确形状”的类型,以便两者都是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建立一个OptionT[Future, A]给定一个Future[Option[A]],而liftM[OptionT]达到相同的Future[A]

在这种情况下res的类型为OptionT[Future, Unit],您可以通过调用Future[Option[Unit]]来获得run

答案 1 :(得分:4)

查看"How does yield work",您的理解力等同于

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

基本上会发生这样的事情:flatMap上的Future被定义为采用返回另一个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]]。正如金建议​​的那样,你可以嵌套理解:

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"
  }
}

如果未定义选项(Future),您也可能导致结果NoSuchElementException失败

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"
  }
}

答案 2 :(得分:2)

为了理解,<-右侧的表达式必须具有兼容的类型。这是因为理解基本上是嵌套flatMap调用的语法糖。要解决此问题,您可以将嵌入在另一个中的理解用于理解,也可以在get上使用mapOption等方法。

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