2013-07-28 16 views
21

E 'possibile catena scala.util.Try e scala.concurrent.Future? Entrambi forniscono effettivamente la stessa interfaccia monadica, ma il tentativo di concatenarli genera un errore di compilazione.Scala - Chaining Futures Prova i blocchi?

Ad esempio. Date le due firme sotto

def someFuture:Future[String] = ??? 
def processResult(value:String):Try[String] = ??? 

è possibile fare qualcosa di simile al seguente?

val result = for(a <- someFuture; b <- processResult(a)) yield b; 
result.map { /* Success Block */ } recover { /* Failure Block */ } 

risultati di questa, ovviamente, in un errore di compilazione, perché Futuro e provare sono in grado di essere flatMapp'ed insieme.

Sarebbe comunque una bella caratteristica essere in grado di incatenarli - è possibile? O devo combinarli in un futuro [Prova [stringa]]?

(In particolare, mi interessa avere un unico blocco "recupero" per rilevare eccezioni nel futuro o nel tentativo).

risposta

27

Di fronte a un problema come questo, in cui si desidera utilizzare tipi diversi in a comprensione, una soluzione può essere quella di provare e selezionare uno dei tipi e mappare l'altro tipo ad esso. Per la tua situazione, date le proprietà uniche (asincrona) dei futures, selezionerei Future come il minimo comune denominatore e mappare lo Try allo Future. Si può semplicemente farlo in questo modo:

val result = for{ 
    a <- someFuture 
    b <- tryToFuture(processResult(a)) 
} yield b 
result.map { /* Success Block */ } recover { /* Failure Block */ } 

def tryToFuture[T](t:Try[T]):Future[T] = { 
    t match{ 
    case Success(s) => Future.successful(s) 
    case Failure(ex) => Future.failed(ex) 
    } 
} 

Ora, se hai trovato questo per essere una situazione molto comune e non ti piaceva costantemente dover aggiungere nella conversione esplicita, suppongo che si possa definire il metodo tryToFuture come implicita su qualche oggetto helper e importarlo dove necessario in questo modo:

object FutureHelpers{ 
    implicit def tryToFuture[T](t:Try[T]):Future[T] = { 
    t match{ 
     case Success(s) => Future.successful(s) 
     case Failure(ex) => Future.failed(ex) 
    } 
    } 
} 

import FutureHelpers._ 
val result = for{ 
    a <- someFuture 
    b <- processResult(a) 
} yield b 
result.map { /* Success Block */ } recover { /* Failure Block */ } 

Basta ricordare che chiamare Future.success e Future.failed ha un impatto su tutto ciò ExecutionContext è portata in che presenterà un altro compito ad essa sotto il cofano.

EDIT

Come Viktor sottolineato nei commenti, il processo di conversione di un Try ad un Future è ancora più facile se si utilizza Future.fromTry come nell'esempio aggiornato dal basso:

val result = for{ 
    a <- someFuture 
    b <- Future.fromTry(processResult(a)) 
} yield b 
result.map { /* Success Block */ } recover { /* Failure Block */ } 

Questa è probabilmente la tua migliore scommessa contro il fare cose con impliciti o la tua logica di conversione.

+4

Attualmente v'è una [discussione su scala-user] (https://groups.google.com/d/topic/scala-user/Mu4_lZAWxz0/discussione) su questo esatto problema di convertire un 'Try' in un' Future'. Forse un aiutante come questo dovrebbe essere incluso nella libreria standard da qualche parte. – gourlaysama

+0

Future.fromProva? –

+0

@ViktorKlang, sì hai ragione. Ho aggiornato la mia risposta per includere questo approccio. Grazie per il testa a testa. – cmbaxter

2

Come su

val result = for(a <- someFuture) yield for(b <- processResult(a)) yield b; 

Anche se non ha un aspetto pulito.

1
implicit def convFuture[T](ft: Future[Try[T]]): Future[T] = 
ft.flatMap { 
    _ match { 
    case Success(s) => Future.successful(s) 
    case Failure(f) => Future.failed(f) 
    } 
} 
2

Forse il problema è vecchio ma attualmente si può:

implicit def tryToFuture[T](t:Try[T]):Future[T] = Promise.fromTry(t).future 
1

C'è anche

Future.fromTry(Try { ... }) 

Così si potrebbe fare

val result = for { 
    a <- someFuture 
    b <- Future.fromTry(processResult(a)) 
} yield b;