web-dev-qa-db-fra.com

Scala "pour la compréhension" avec les futurs

Je lis le livre de recettes Scala ( http://shop.oreilly.com/product/0636920026914.do )

Il y a un exemple lié à l'utilisation future qui implique la compréhension.

Jusqu'à présent, ma compréhension de la compréhension est lorsque l'utilisation avec une collection produira une autre collection du même type. Par exemple, si chaque futureX est de type Future[Int], les éléments suivants doivent également être de type Future[Int]:

for {
   r1 <- future1
   r2 <- future2
   r3 <- future3
} yield (r1+r2+r3)

Quelqu'un pourrait-il m'expliquer ce qui se passe exactement lorsque <- est utilisé dans ce code? Je sais que s'il s'agissait d'un générateur, il récupérera chaque élément en boucle.

49
nish1013

D'abord pour comprendre. Le SO a répondu à maintes reprises qu'il s'agissait d'une abstraction sur deux opérations monadiques: map, flatMap, withFilter. Lorsque vous utilisez <-, scalac désugare ces lignes en monadique flatMap:

r <- monad dans monad.flatMap(r => ... )

cela ressemble à un calcul impératif (en quoi consiste une monade), vous liez un résultat de calcul à r. Et la partie yield est décomposée en appel map. Le type de résultat dépend du type de monad 's.

Le trait Future a une fonction flatMap et map, nous pouvons donc l'utiliser pour la compréhension. Dans votre exemple, vous pouvez utiliser le code suivant:

future1.flatMap(r1 => future2.flatMap(r2 => future3.map(r3 => r1 + r2 + r3) ) )

Parallélisme mis à part

Il va sans dire que si l'exécution de future2 dépend de r1, vous ne pouvez pas échapper à l'exécution séquentielle, mais si les calculs futurs sont indépendants, vous avez le choix. Vous pouvez appliquer une exécution séquentielle ou allow pour une exécution parallèle. Vous ne pouvez pas appliquer ce dernier, car le contexte d'exécution le gérera.

val res = for {
   r1 <- computationReturningFuture1(...)
   r2 <- computationReturningFuture2(...)
   r3 <- computationReturningFuture3(...)
} yield (r1+r2+r3)

sera toujours exécuté de manière séquentielle. Cela peut être facilement expliqué par le desugaring, après quoi les appels computationReturningFutureX suivants sont uniquement appelés à l'intérieur des flatMaps, c'est-à-dire.

computationReturningFuture1(...).flatMap(r1 => 
    computationReturningFuture2(...).flatMap(r2 => 
        computationReturningFuture3(...).map(r3 => r1 + r2 + r3) ) )

Cependant, cela peut fonctionner en parallèle et pour la compréhension agrège les résultats:

val future1 = computationReturningFuture1(...)
val future2 = computationReturningFuture2(...)
val future3 = computationReturningFuture3(...)

val res = for {
   r1 <- future1
   r2 <- future2
   r3 <- future3
} yield (r1+r2+r3)
116
4lex1v

Pour élaborer ces réponses existantes, voici un résultat simple montrant comment fonctionne la compréhension for.

Ses fonctions sont un peu longues, mais elles valent la peine d’être examinées.

Une fonction qui nous donne une plage d'entiers

scala> def createIntegers = Future{
             println("INT "+ Thread.currentThread().getName+" Begin.")
             val returnValue = List.range(1, 256)
             println("INT "+ Thread.currentThread().getName+" End.")
             returnValue
         }
createIntegers: createIntegers: scala.concurrent.Future[List[Int]]

Une fonction qui nous donne une gamme de caractères

scala> def createAsciiChars = Future{
             println("CHAR "+ Thread.currentThread().getName+" Begin.")
             val returnValue = new ListBuffer[Char]
             for (i <- 1 to 256){
                  returnValue += i.toChar
             }
             println("CHAR "+ Thread.currentThread().getName+" End.")
             returnValue
          }
createAsciiChars: scala.concurrent.Future[scala.collection.mutable.ListBuffer[Char]]

Utiliser ces appels de fonction dans le pour la compréhension.

scala> val result = for{
                        i <- createIntegers
                        s <- createAsciiChars
                    } yield i.Zip(s)
       Await.result(result, Duration.Inf)
result: scala.concurrent.Future[List[(Int, Char)]] = Future(<not completed>)

Pour les lignes ci-dessous, nous pouvons vérifier que tous les appels de fonction sont synchrones, c'est-à-dire que l'appel de fonction createAsciiChars est non exécuté jusqu'à ce que createIntegers termine son exécution. 

scala> INT scala-execution-context-global-27 Begin.
       INT scala-execution-context-global-27 End.
       CHAR scala-execution-context-global-28 Begin.
       CHAR scala-execution-context-global-28 End.

Faire ces appels de fonction createAsciiChars, createIntegers en dehors de la compréhension for sera une exécution asynchrone.

0
Puneeth Reddy V