Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better syntax for recovery from a for comprehension

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?

like image 752
Arne Claassen Avatar asked Sep 08 '14 20:09

Arne Claassen


People also ask

What operations is a for comprehension syntactic sugar for?

In short - a for comprehension gives us a syntactic sugar for working with large, sequenced and/or nested computations using flatMap , map and filter .

What is for comprehension in Scala?

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.

What is for yield in Scala?

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.


1 Answers

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.

like image 141
som-snytt Avatar answered Dec 22 '22 04:12

som-snytt