在以下代码段中,
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没有显示任何错误,也没有显示任何错误,并且类型看起来是对齐的。但是我不确定编译器为什么不开心!有线索吗?
答案 0 :(得分:15)
您的理解类型必须一致,因此您无法以您的方式自由地混合Option
和Future
。
在您的示例中,f1
返回Future[Option[MyType1]]
,f2
返回Future[MyType2]
请记住,对于一系列flatMap
/ map
以及可能withFilter
的一系列理解是荒谬的。
flatMap
和Future[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,它允许您将(例如)Future
和Option
组合成单个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
上使用map
或Option
等方法。
另一种选择是使用monad transformers,但这超出了这个答案的范围。