I have a number of functions that return a future that is the result of a for comprehension, but i need to need to recover from some possible failures on the way out. The standard syntax seems to capture the for comprehension as an intermediate results like so:
def fooBar(): Future[String] = {
val x = for {
x <- foo()
y <- bar(x)
} yield y
x.recover {
case SomeException() => "bah"
}
}
The best alternative to I've found is to wrap the whole for comprehension in parentheses:
def fooBar(): Future[String] = (for {
x <- foo()
y <- bar(x)
} yield y).recover {
case SomeException() => "bah"
}
This mostly seems like a shortcut than an improvement in syntax, so I'm wondering if there is a better way to weave recovery into for comprehensions?
In short - a for comprehension gives us a syntactic sugar for working with large, sequenced and/or nested computations using flatMap , map and filter .
Scala offers a lightweight notation for expressing sequence comprehensions. Comprehensions have the form for (enumerators) yield e , where enumerators refers to a semicolon-separated list of enumerators. An enumerator is either a generator which introduces new variables, or it is a filter.
Yield is a keyword in scala that is used at the end of the loop. We can perform any operation on the collection elements by using this for instance if we want to increment the value of collection by one. This will return us to the new collection.
Some brace adjustment helps, though some people prefer braces instead of parens for a multiline expression:
scala> def f = (
| for {
| x <- foo;
| y <- bar(x)
| } yield y
| ) recover {
| case _: NullPointerException => -1
| }
f: scala.concurrent.Future[Int]
if you don't like
scala> foo flatMap bar recover { case _: NullPointerException => -1 }
res9: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@3efe7086
You can go all syntaxy:
object Test extends App {
import concurrent._
import duration.Duration._
import ExecutionContext.Implicits._
type ~>[A, B] = PartialFunction[A, B]
type NPE = NullPointerException
class `recovering future`[A, R >: A](val f: Future[A], val pf: Throwable ~> R) {
def map[B >: A <: R](m: A => B) = new `recovering future`[B, R](f map m, pf)
def flatMap[B >: A <: R](m: A => Future[B]) = new `recovering future`[B, R](f flatMap m, pf)
def recovered: Future[R] = f recover pf
}
object `recovering future` {
implicit def `back to the future`[A, R >: A](x: `recovering future`[A, R]): Future[R] = x.recovered
}
implicit class `inline recoverer`[A](val f: Future[A]) {
def recovering[B >: A](pf: Throwable ~> B) = new `recovering future`(f, pf)
}
def f = Future(8)
def g(i: Int) = Future(42 + i)
def e(i: Int): Future[Int] = Future((null: String).length)
Unadorned:
for {
x <- f
y <- g(x)
} Console println y // 50
And with the recover inlined:
def compute: Future[Int] =
for {
x <- f recovering { case _: NPE => -1 }
y <- g(x)
} yield y
Console println (Await result (compute, Inf)) // 50
Or showing the failing case:
def fail: Future[Int] =
for {
x <- f recovering { case _: NPE => -1 }
y <- e(x)
} yield y
Console println (Await result (fail, Inf)) // -1
}
if you swing that way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With