Scala Future with filter in for comprehension
Asked Answered
B

4

24

In the example below I get the exception java.util.NoSuchElementException: Future.filter predicate is not satisfied

I want to have the result Future( Test2 ) when the check if( i == 2 ) fails. How do I handle filter/if within a for comprehension that deals with composing futures?

Below is a simplified example that works in the 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
Buttocks answered 25/7, 2013 at 22:7 Comment(0)
H
13

In your for-comprehension, you are filtering by i == 2. Because the value of f1 is not two, it will not yield a Success but instead a Failure. The predicate of the filter is not satisfied, as your errror message tells you. However, f2.recover returns a new Future. The value of f2 is not manipulated. It still stores the Failure. That is the reason you get the error message when you call f2.value.

The only alternative I can think of would be using an else in your for-comprehension as shown here.

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

This will return Some(Success(Test2)) as your f3.value does.

Hirudin answered 26/7, 2013 at 7:31 Comment(1)
However if f2 fails because of "i <- f1" failing, the result will not be "Test2" but still a Failure.Kongo
K
31

This is a more idiomatic solution, in my opinion. This predicate function creates either a Future[Unit] or a failed future containing your exception. For your example, this would result in either a Success("Test1") or a Failure(Exception("Test2")). This is slightly different from "Test1" and "Test2", but I find this syntax to be more useful.

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

You use it like this:

val f2 = for {
  i <- f1
  _ <- predicate( i == 2 )(new Exception("Test2"))
  j <- f3  // f3 will only run if the predicate is true
} yield "Test1"
Kenzie answered 9/3, 2014 at 22:29 Comment(1)
In your predicate method, you're better off using Future.successful(()) than Future(())Bubal
H
13

In your for-comprehension, you are filtering by i == 2. Because the value of f1 is not two, it will not yield a Success but instead a Failure. The predicate of the filter is not satisfied, as your errror message tells you. However, f2.recover returns a new Future. The value of f2 is not manipulated. It still stores the Failure. That is the reason you get the error message when you call f2.value.

The only alternative I can think of would be using an else in your for-comprehension as shown here.

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

This will return Some(Success(Test2)) as your f3.value does.

Hirudin answered 26/7, 2013 at 7:31 Comment(1)
However if f2 fails because of "i <- f1" failing, the result will not be "Test2" but still a Failure.Kongo
B
10

Of course I figured out one solution myself. Perhaps there are better, more idiomatic, solutions?

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
Buttocks answered 25/7, 2013 at 22:15 Comment(3)
It is mentioned in the docs as well: docs.scala-lang.org/overviews/core/… no when I take a second look.Buttocks
I think using recover here is okay. If you want your recover to only apply to the filter predicate based exception, then you should change the case _ to case ex:NoSuchElementException where NoSuchElementException comes from java.util.Vivanvivarium
Jürgen: This is a six months old question, the code above was an example taken out of a REPL session, so it is geared towards clarity. You're commenting on that you can put parenthesis around statements in Scala? Not seeing what your contribution is here.Buttocks
R
0

I liked @pkinsky 's idea, and made a bit of improvement. I dropped code to create Exception object like this:

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

shouldTrue function is implemented using lihaoyi`s sourcecode library:

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}"))

Then it automatically generates more meaningful exception message:

java.lang.Exception: i == 2 returns false
    at \path\to\example.scala:17
Revalue answered 20/8, 2018 at 4:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.