Search code examples
scalafuturefor-comprehension

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?


Solution

  • 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.