web-dev-qa-db-fra.com

Scala Future avec filtre pour la compréhension

Dans l'exemple ci-dessous, je reçois l'exception Java.util.NoSuchElementException: Future.filter predicate is not satisfied

Je veux avoir le résultat Future( Test2 ) lorsque la vérification if( i == 2 ) échoue. Comment est-ce que je manipule le filtre/si dans une compréhension qui traite de la composition de futurs?

Vous trouverez ci-dessous un exemple simplifié qui fonctionne dans le Scala REPL.

Code:

import scala.concurrent.Future
import scala.util.{ Try, Success, Failure }
import scala.concurrent.ExecutionContext.Implicits.global

val f1 = Future( 1 )
val f2 = for {
  i <- f1
  if( i == 2 )
} yield "Test1"
f2.recover{ case _ => "Test2" }
f2.value
22
Magnus

Dans votre for-comprehension, vous filtrez par i == 2. Étant donné que la valeur de f1 n’est pas deux, elle ne générera pas une Success mais plutôt une Failure. Le prédicat du filtre n'est pas satisfait, comme l'indique votre message d'erreur. Cependant, f2.recover renvoie une nouvelle Future. La valeur de f2 n'est pas manipulée. Il stocke toujours la Failure. C'est la raison pour laquelle vous recevez le message d'erreur lorsque vous appelez f2.value.

La seule alternative à laquelle je puisse penser serait d'utiliser une else dans votre for-comprehension, comme indiqué ici .

val f2 = for ( i <- f1) yield {
  if (i == 2) "Test1"
  else "Test2"
}
f2.value

Cela retournera Some(Success(Test2)) comme votre f3.value le fait.

11
tgr

C'est une solution plus idiomatique, à mon avis. Cette fonction de prédicat crée soit un Future[Unit], soit un futur échoué contenant votre exception. Pour votre exemple, cela entraînerait soit une Success("Test1"), soit une Failure(Exception("Test2")). Ceci est légèrement différent de "Test1" et "Test2", mais je trouve cette syntaxe plus utile.

def predicate(condition: Boolean)(fail: Exception): Future[Unit] = 
    if (condition) Future( () ) else Future.failed(fail)

Vous l'utilisez comme ceci:

val f2 = for {
  i <- f1
  _ <- predicate( i == 2 )(new Exception("Test2"))
  j <- f3  // f3 will only run if the predicate is true
} yield "Test1"
29
pkinsky

Bien sûr, j'ai trouvé une solution moi-même. Peut-être y at-il de meilleures solutions, plus idiomatiques?

import scala.concurrent.Future
import scala.util.{ Try, Success, Failure }
import scala.concurrent.ExecutionContext.Implicits.global

val f1 = Future( 1 )
val f2 = for {
  i <- f1
  if( i == 2 )
} yield "Test1"
val f3 = f2.recover{ case _ => "Test2"  }
// OR val f3 = f2.fallbackTo( Future( "Test2" ) )
f3.value
8
Magnus

J'ai bien aimé l'idée de @pkinsky et fait quelques améliorations. J'ai abandonné le code pour créer un objet Exception comme ceci:

val f2 = for {
  i <- f1
  _ <- shouldTrue( i == 2 )
  j <- f3  // f3 will only run if the predicate is true
} yield "Test1"

La fonction shouldTrue est implémentée à l’aide de code source bibliothèque de lihaoyi:

def shouldTrue(condition: sourcecode.Text[Boolean])(implicit enclosing: sourcecode.Enclosing, file: sourcecode.File, line: sourcecode.Line): Future[Unit] =
  if (condition.value) Future.successful( () ) else Future.failed(new Exception(s"${condition.source} returns false\n\tat ${file.value}:${line.value}"))

Ensuite, il génère automatiquement un message d'exception plus significatif:

Java.lang.Exception: i == 2 returns false
    at \path\to\example.scala:17
0
pocorall