2015-07-20 15 views
5

Nel seguente frammento di codice,Scala Futuro [Opzione [T]] Un imballaggio

trait MyType1; trait MyType2 
import scala.concurrent.Promise 

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

passo in P1 e P2 ad un'altra funzione, dove io completo la promessa con un futuro di successo. Dopo la chiamata a questa funzione, cerco di leggere il valore della Promessa:

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

Quando provo a compilare questo, ottengo alcuni problemi di compilazione.

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

IntelliJ non mostra errori o che cosa mai ei tipi sembrano allineati. Ma non sono sicuro del motivo per cui il compilatore è infelice! Qualche indizio?

risposta

15

I tipi di comprensione devono essere coerenti, quindi non è possibile mescolare liberamente Option e Future nel modo che si fa.

Nel tuo esempio, f1 restituisce un Future[Option[MyType1]] mentre f2 restituisce un Future[MyType2]

ricordare che una di comprensione desugars ad una serie di flatMap/map e potenzialmente withFilter.

Anche i (semplificato) firme di flatMap per Future[A] e Option[A] sono

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

Le prime due fasi del desugar per-la comprensione di

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

vedere l'errore ora?


Ora, per risolvere questo problema è possibile seguire alcuni approcci. È molto utile utilizzare Monad Transformers, che consente di combinare (ad esempio) Future e Option in un unico tipo monad OptionT.

In particolare è possibile andare avanti e indietro da Future[Option[A]] a OptionT[Future, A]. L'idea di base è che puoi flatMap su quest'ultimo ed estrarre un valore di tipo A.

Ma prima di questo, è necessario per rendere il vostro tipi della "forma giusta", in modo che entrambi sono un Future[Option[Something]]

Ecco un esempio utilizzando 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 costruisce un OptionT[Future, A] dato un Future[Option[A]] , mentre liftM[OptionT] raggiunge lo stesso quando si dispone di un Future[A]

Il tipo di res è OptionT[Future, Unit] in questo caso, ed è possibile ottenere un Future[Option[Unit]] chiamando lo run su di esso.

2

In una comprensione, le espressioni sul lato destro del <- devono avere tipi compatibili. Questo perché le comprensioni sono fondamentalmente zucchero sintattico per le chiamate nidificate flatMap. Per risolvere questo problema, puoi avere una comprensione per nidificare all'interno di un'altra per comprensione oppure puoi usare metodi come get o map su Option s.

Un'altra opzione è utilizzare monad transformers, ma questo va oltre lo scopo di questa risposta.

4

Guardando "How does yield work", il vostro per la comprensione equivale a

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

In sostanza ciò che accade è questo: flatMap su una Future è definito a prendere una funzione che restituisce un'altra Future. Questo vi dà un errore simile:

<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 => ^

Se si desidera che l'operazione da eseguire in modo asincrono, vale a dire in realtà la mappatura dei Future casi, si dovrà tornare a un Future[Option[X]]. Come suggerito Kim, è possibile nidificare la comprensione:

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

Si potrebbe anche fare il conseguente Future falliscono quando l'opzione non è definito (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" 
    } 
} 
Problemi correlati