2015-07-20 57 views
5

在下面的代碼段中,Scala的未來[選項[T]]聯合國包裝

trait MyType1; trait MyType2 
import scala.concurrent.Promise 

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

我通過在p1和p2到另一個函數,其中我使用一個成功的未來完成的前景。呼叫此功能後,我嘗試讀取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顯示沒有錯誤或者什麼都沒有,並且類型看起來是一致的。但我不確定編譯器爲什麼不快樂!任何線索?

回答

15

您的理解類型必須一致,因此您不能像您那樣自由混合OptionFuture

在你的榜樣,f1返回Future[Option[MyType1]]f2回報Future[MyType2]

請記住,對於修真desugars一系列的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] 

前兩個步驟進行,理解desugar來

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

看到的錯誤呢?


現在,要解決這個問題,有幾種方法可以遵循。一個非常方便的是使用Monad變形金剛,它允許您將(例如)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]在這種情況下,你可以通過調用run得到Future[Option[Unit]]。在"How does yield work"

2

爲了便於理解,<-右側的表達式必須具有兼容的類型。這是因爲對於嵌套flatMap調用來說,理解基本上是語法糖。爲了解決這個問題,你可以有理解嵌套在另一個理解,或者你可以在Option上使用像getmap這樣的方法。

另一種選擇是使用monad transformers,但這超出了本答案的範圍。

4

看,你的理解是相當於

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

基本上會發生什麼情況是這樣的:在一個FutureflatMap被定義爲採取返回另一個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[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" 
    } 
}