2015-02-05 4 views
7

Ho letto molte volte su Scala Futures riducendo i problemi di callback. Ho un codice che ha iniziato a sembrare problematico.Scala Futures callback hell

val a = Future(Option(Future(Option(10)))) 

a.map { b => 
    b.map { c => 
    c.map { d => 
     d.map { res => 
     res + 10 
     } 
    } 
    } 
} 

Come posso rendere questo codice più piatto?

// Modifica @againstmethod

for{ 
    b <- a 
    c <- b 
    d <- c 
    res <- d 
} yield res + 10 

Questo codice non compilerà

Error:(21, 8) type mismatch; found : Option[Int] required:
scala.concurrent.Future[?] res <- d
^

+4

se si avvicinò con un esempio che compila in realtà quando si incolla in un IDE, potrebbe essere più facile per suggerire miglioramenti ... –

+0

@KimStebel appena cambiato il codice di quindi è più universale –

+0

Si dovrebbe evitare di impilare 'Future' in primo luogo, usando' Future.traverse' o 'Future.sequence'. – Dimitri

risposta

1

In realtà la risposta era abbastanza semplice.

for { 
a <- b 
c <- a.get 
} yield c.get + 10 

sembra essere sufficiente, perché quando x.get + 10 fallisce (a causa della None + 10) il futuro proprio non riesce. Così funziona ancora utilizzare un semplice ripiego

val f = for { 
a <- b 
c <- a.get 
} yield c.get + 10 
f fallbackTo Future.successful(0) 
2

È possibile utilizzare un for comprehension. Nell'esempio:

import scala.concurrent.Future 
import scala.concurrent.ExecutionContext.Implicits.global 

object Stuff extends App { 
    val result = for { 
    f1 <- Future { 10 + 1 } 
    f2 <- Future { f1 + 2 } 
    } yield f2 
    result.onComplete(println) 
} 

Dove risultato sarà 13.

Qualsiasi classe che implementa una funzione propria map e flatMap può essere usato in questo modo in un for.

Se non ti dispiace un'altra dipendenza, si può anche usare una libreria come scalaz ed esplicitamente usare monadica vincolante per appiattire le cose (EDIT codificato alcuni tipi di opzioni per affrontare un commento qui sotto):

import scalaz._ 
import Scalaz._ 
import scala.concurrent._ 
import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration._ 
import scala.util.{Success,Failure} 

object BindEx extends App { 

    def f1(i: String): Future[Int] = Future { i.length } 
    def f2(i: Int): Future[Option[Double]] = Future { Some(i/Math.PI) } 
    def f3(i: Option[Double]): Future[Option[Double]] = Future { 
    i match { 
     case Some(v) => Some(Math.round(v)) 
     case _ => None 
    } 
    } 

    val result = 
    Monad[Future].point("Starting Point") >>= 
    f1 >>= 
    f2 >>= 
    f3 

    result.onComplete { x => 
    x match { 
     case Success(value) => println("Success " + value) 
     case Failure(ex) => println(ex) 
    } 
    } 

    Await.result(result, 1 seconds) 
} 

e, infine, se avete appena operazioni parallele che si desidera legare dopo tutto sono riusciti che sono indipendenti, è possibile utilizzare scalaz builder applicativa:

val result = (
    Future { 10 + 10 } |@| 
    Future { 20 - 3 } |@| 
    Future { Math.PI * 15 } 
) { _ + _/_} 
    println(Await.result(result, 1 seconds)) 

Questo vi permetterà di tutti e 3 i futures completa, quindi applicare blocco a 3 argomenti.

+0

Sì, è vero. Ma per la comprensione non restituisce nulla in caso di fallimento, giusto? –

+0

Correggere e se li crei in linea in quel modo verranno eseguiti in serie. È solo un altro strumento che può aiutare. –

+0

'result' è un' Future' nel modo in cui ha senso; puoi usare 'result.onFailure' o simile per gestire i fallimenti. Non svaniscono silenziosamente. – lmm

0

Non li ho ancora usati, ma dovrebbero essere esattamente ciò che state cercando: trasformatori Monad.

In sostanza, un trasformatore monad accetta una Monade (ad esempio Future) e aggiunge funzionalità, come la funzionalità fornita da Option, e restituisce una Monade trasformata. Penso che ci sia anche un trasformatore Option Monad in Scalaz. Questo dovrebbe permetterti di usare le opzioni annidate in Futures e avere ancora una struttura di codice piatta usando per le comprensioni.

Vedere http://blog.garillot.net/post/91731853561/a-question-about-the-option-monad-transformer e https://softwarecorner.wordpress.com/2013/12/06/scalaz-optiont-monad-transformer/ per alcuni esempi.