Scala Language
Futures
Ricerca…
Creare un futuro
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object FutureDivider {
def divide(a: Int, b: Int): Future[Int] = Future {
// Note that this is integer division.
a / b
}
}
Molto semplicemente, il metodo di divide crea un futuro che si risolverà con il quoziente di a b .
Consumare un futuro di successo
Il modo più semplice per consumare un futuro di successo - o meglio, ottenere il valore all'interno del futuro - è utilizzare il metodo della map . Supponiamo che alcuni codici chiamino il metodo di divide dell'oggetto FutureDivider dall'esempio "Creating a Future". Come dovrebbe apparire il codice per ottenere il quoziente di a b ?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Consumare un futuro fallito
A volte il calcolo in un futuro può creare un'eccezione, che farà fallire il futuro. Nell'esempio "Creating a Future", cosa accadrebbe se il codice chiamante passasse 55 e 0 al metodo divide ? Avrebbe gettato un ArithmeticException dopo aver provato a dividere per zero, ovviamente. Come sarebbe gestito nel codice che consuma? Ci sono in realtà una manciata di modi per affrontare i fallimenti.
Gestire l'eccezione con il recover e la corrispondenza del modello.
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Gestire l'eccezione con il failed proiezione, dove l'eccezione diventa il valore del futuro:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
// Note the use of the dot operator to get the failed projection and map it.
eventualQuotient.failed.map {
ex => println(s"It failed with: ${ex.getMessage}")
}
}
}
Mettere insieme il futuro
Gli esempi precedenti hanno dimostrato le caratteristiche individuali di un futuro, gestendo casi di successo e fallimento. Di solito, tuttavia, entrambe le funzionalità vengono gestite in modo molto più preciso. Ecco l'esempio, scritto in modo più ordinato e più realistico:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(s"Quotient: $quotient")
} recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Sequencing and traversing Futures
In alcuni casi è necessario calcolare una quantità variabile di valori su Futures separati. Si supponga di avere una List[Future[Int]] , ma invece una List[Int] deve essere elaborata. Quindi la domanda è come trasformare questa istanza di List[Future[Int]] in un Future[List[Int]] . Per questo scopo c'è il metodo di sequence sull'oggetto compagno Future .
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
In generale, la sequence è un operatore comunemente noto nel mondo della programmazione funzionale che trasforma F[G[T]] in G[F[T]] con restrizioni a F e G
Esiste un operatore alternativo chiamato traverse , che funziona in modo simile ma prende una funzione come argomento aggiuntivo. Con la funzione di identità x => x come parametro si comporta come l'operatore di sequence .
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Tuttavia, l'argomento extra consente di modificare ogni istanza futura all'interno della listOfFuture . Inoltre, il primo argomento non deve essere una lista di Future . Pertanto è possibile trasformare l'esempio come segue:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
In questo caso l' List(1,2,3) viene passato direttamente come primo argomento e la funzione di identità x => x viene sostituita con la funzione Future(_) per avvolgere in modo simile ciascun valore Int in un Future . Un vantaggio di questo è che l' List[Future[Int]] intermedio List[Future[Int]] può essere omesso per migliorare le prestazioni.
Combina più Futures - Per Comprensione
La comprensione è un modo compatto per eseguire un blocco di codice che dipende dal risultato positivo di più futuri.
Con f1, f2, f3 tre Future[String] che conterranno le stringhe one, two, three rispettivamente,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined sarà una Future[String] contenente la stringa one - two - three una volta che tutti i futures sono stati completati con successo.
Si noti che qui viene assunto un implicito ExectionContext.
Inoltre, tieni a mente che per la comprensione è solo uno zucchero sintattico per un metodo flatMap, quindi la costruzione di oggetti futuri all'interno del corpo eliminerebbe l'esecuzione simultanea di blocchi di codice racchiusi dai futures e condurrebbe al codice sequenziale. Lo vedi nell'esempio:
val result1 = for {
first <- Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
second <- Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
} yield first - second
val fut1 = Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
val fut2 = Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
val result2 = for {
first <- fut1
second <- fut2
} yield first - second
Il valore racchiuso dall'oggetto result1 sarebbe sempre negativo mentre il result2 sarebbe positivo.
Per maggiori dettagli sulla comprensione e sulla yield in generale, vedi http://docs.scala-lang.org/tutorials/FAQ/yield.html