2013-04-30 22 views
5

Ho cercato di semplificare il modo in cui faccio i futures in Scala. Ho ottenuto ad un punto un Future[Option[Future[Option[Boolean]] ma l'ho semplificato più sotto. C'è un modo migliore per semplificare questo?Futuro [Opzione [Futuro [Opzione [Booleano]] Semplificando Futures e Opzioni?

Passare un futuro di "fallito" non corrisponde a come il modo migliore per farlo. nel mondo sequenziale ho semplicemente restituito "FAIL !!" ogni volta falliva piuttosto che continuare fino alla fine. Ci sono altri modi?

val doSimpleWork = Future { 
    //Do any arbitrary work (can be a different function) 
    true //or false 
} 

val doComplexWork = Future { 
    //Do any arbitrary work (can be a different function) 
    Some("result") //or false 
} 

val failed = Future { 
    //Do no work at all!!! Just return 
    false 
} 

val fut1 = doSimpleWork 
val fut2 = doSimpleWork 

val fut3 = (fut1 zip fut2).map({ 
    case (true, true) => true 
    case _ => false 
}) 

val fut4 = fut3.flatMap({ 
    case true => 
    doComplexWork.flatMap({ 
     case Some("result") => 
     doSimpleWork 
     case None => 
     failed 
    }) 
    case false => 
    failed 
}) 

fut4.map({ 
    case true => 
    "SUCCESS!!!" 
    case _ => 
    "FAIL!!" 
}) 
+0

Ho l'impressione che la logica degli errori sia diventata eccessivamente complicata. Ci sono 3 possibili modi per gestire i fallimenti in questo frammento: futures, opzioni, booleans falliti. Suggerirei di semplificare questa situazione come primo passo, magari convertendo diversi fallimenti in una forma preferita (ad esempio, solo attenersi a futuri risultati non riusciti). –

risposta

3

Si noti che nel tuo esempio, perché si sta avidamente istanziare la Futures ad un val, tutti loro inizierà l'esecuzione non appena li (val x = Future {...}) si dichiara. L'utilizzo dei metodi renderà i Futures eseguiti solo quando vengono richiesti dalla catena di esecuzione.

Un modo per evitare l'ulteriore calcolo sarebbe un'eccezione, quindi gestirlo con onFailure:

def one = future { println("one") ; Some(1) } 
def two = future { println("two") ; throw new Exception("no!"); 2 } 
def three = future { println("three") ; 3 } 

val f = one flatMap { 
    result1 => two flatMap { 
    result2 => three 
    } 
} 

f onFailure { 
    case e: Exception => 
    println("failed somewhere in the chain") 
} 

Si può vedere qui che "tre" non dovrebbe essere stampato, perché noi fail su two. Questo è il caso:

one 
two 
failed somewhere in the chain 
+0

Non sapevo che 'val x = Future {...} 'in realtà l'ha istanziato .. definire una funzione è un ottimo consiglio !!! Userò anche 'onFailure' o' fallbackTo' per catturare l'errore. Grazie! –

1

Si potrebbe provare qualcosa di simile, utilizzando per comprehensions per ripulire il codice un po ':

def doSimpleWork = Future{ 
    //do some simple work 
    true 
    } 

    def doComplexWork = Future{ 
    //do something complex here 
    Some("result") 
    } 

    val fut1 = doSimpleWork 
    val fut2 = doSimpleWork 

    val fut = for{ 
    f1Result <- fut1 
    f2Result <- fut2 
    if (f1Result && f2Result) 
    f3Result <- doComplexWork 
    if (f3Result.isDefined) 
    f4Result <- doSimpleWork 
    } yield "success" 

    fut onComplete{ 
    case Success(value) => println("I succeeded") 
    case Failure(ex) => println("I failed: " + ex.getMessage) 
    } 

E se si voleva davvero solo di stampare "successo" o "fallito" alla fine, si potrebbe cambiare quell'ultimo pezzo di codice a:

fut.recover{case ex => "failed"} onSuccess{ 
    case value => println(value) 
    } 

Ora, per spiegare cosa sta succedendo. Per cominciare, abbiamo definito due funzioni che restituiscono Futures che stanno facendo un lavoro asincrono. La funzione doSimpleWork eseguirà un lavoro semplice e restituirà un indicatore di successo/errore booleano. La funzione doComplexWork farà qualcosa di più complesso (e dispendioso in termini di tempo) e restituirà un'opzione [String] che rappresenta un risultato. Quindi iniziamo due invocazioni parallele di doSimpleWork prima di entrare nella comprensione. In per comp, otteniamo i risultati di fut1 e fut2 (in questo ordine) prima di verificare se entrambi hanno avuto successo. Se no, ci fermeremmo qui, e il valore fut sarebbe fallito con un NoSuchElementException che è quello che ottieni quando una condizione come questa non riesce in un comp. Se entrambi avessero avuto successo, avremmo continuato e richiamato la funzione doComplexWork e aspettiamo il suo risultato. Verificheremo quindi il suo risultato e, se fosse Some, daremo il via all'ultima chiamata di doSimpleWork. Se ciò dovesse succedere, daremmo la stringa "successo". Se si controlla il tipo di fut val, è di tipo Future[String].

Da lì, utilizziamo una delle funzioni di richiamata asincrona per verificare se l'intera sequenza di chiamate ha superato il tutto (il caso Success) o non è riuscita da qualche parte nel processo (il caso Failure), stampando qualcosa legato al caso in cui ha colpito. Nel blocco di codice finale alternativo, si ripristina da eventuali errori restituendo la stringa "non riuscita" e quindi utilizzare solo la richiamata onSuccess che stamperà "riuscita" o "non riuscita" a seconda di cosa è successo.

+0

L'uso di un 'for' è una buona idea. Stavo provando 'filter' ma un' for' li ha integrati. Grazie. –

3

a " Monad transformer "è un costrutto che consente di combinare gli" effetti "di due monadi, il progetto scalaz fornisce diversi trasformatori monad.Il mio suggerimento è che è possibile utilizzare il trasformatore monad OptionT per semplificare il codice se si fa anche uso del fatto che Option[Unit] è isomorfo a Boolean (Some(()) == true e None == false). Ecco un esempio completo:

import scalaz._ 
import Scalaz._ 
import scala.concurrent._ 
import ExecutionContext.Implicits.global 
import scala.concurrent.duration._ 
object Foo { 

    // We need a Monad instance for Future, here is a valid one, or you can use the implementation 
    // in the scalaz-contrib project, see http://typelevel.org 
    implicit def futureMonad(implicit executor: ExecutionContext): Monad[Future] = new Monad[Future] { 
    override def bind[A, B](fa: Future[A])(f: A ⇒ Future[B]) = fa flatMap f 
    override def point[A](a: ⇒ A) = Future(a) 
    override def map[A, B](fa: Future[A])(f: A ⇒ B) = fa map f 
    } 

    // OptionT allows you to combine the effects of the Future and Option monads 
    // to more easily work with a Future[Option[A]] 
    val doSimpleWork : OptionT[Future,Unit] = OptionT(Future { 
    // Option[Unit] is isomorphic to Boolean 
    Some(()) //or None 
    }) 

    val simpleFail : OptionT[Future,Unit] = OptionT(Future { 
    None 
    }) 

    val doComplexWork: OptionT[Future,String] = OptionT(Future { 
    Some("result") //or None 
    }) 

    val f1 = doSimpleWork 
    val f2 = doSimpleWork 
    val f3 = doComplexWork 
    val f4 = doSimpleWork 

    def main(argv: Array[String]) { 
    val result = for { 
     _ <- f1 
     // we don't get here unless both the future succeeded and the result was Some 
     _ <- f2 
     _ <- f3 
     r <- f4 
    } yield(r) 

    result.fold((_ => println("SUCCESS!!")),println("FAIL!!")) 

    // "run" will get you to the Future inside the OptionT 
    Await.result(result.run, 1 second) 
    } 
} 
+0

Questo è davvero pulito e interessante. Grazie –

Problemi correlati