Jas
Jas

Reputation: 15093

How to have succinct and correct version of error handling in succinct and correct version of functional pipelining in scala?

effective scala style states the following:

this is both succinct and correct, but nearly every reader will have a difficult time recovering the original intent of the author. A strategy that often serves to clarify is to name intermediate results and parameters:

val votesByLang = votes groupBy { case (lang, _) => lang }
val sumByLang = votesByLang map { case (lang, counts) =>
  val countsOnly = counts map { case (_, count) => count }
  (lang, countsOnly.sum)
}
val orderedVotes = sumByLang.toSeq
  .sortBy { case (_, count) => count }
  .reverse

what I fail to understand is in this style if i have errors while evaluating the value for votesByLang, sumByLang, ... how can I have a single recover at the end after all if i just had .map.map ... I could have a single recover at the end. So is it possible to have a single recover also with this style? (and not only possible but with good style...)

Or in other words what is the succinct and correct error handling in this succinct and correct functional pipelining code?

you see with the non-concise version i'm able to code this:

object MyRealMainObj extends App {

  println(
    Try(1)
      .map(doOne)
      .map(doTwo)
      .recover { // non succinct version can catch any prior error how to achieve the same in succinct version
        case e: Throwable => println("recovering from: " + e.getMessage)
    }
  )

  def doOne(i: Int): Int = { i + 1; throw new RuntimeException("failed in one") }
  def doTwo(i: Int): Int = { i + 2; throw new RuntimeException("failed in two") }
}

a single recover will catch any previous error in the .map but with the concise-succinct how can I achieve the same in concise and succinct way?

Upvotes: 0

Views: 39

Answers (1)

Didier Dupont
Didier Dupont

Reputation: 29528

It sound like you expect some sort of scope to be magically created by .recover. This is not the case. With some minor simplifications, Try(expr) is

try {
   val result = expr
   Success(result)
catch {
   e => Failure(e)
}

tryResult.map(f) is :

tryResult match {
   case Success(x) => Try(f(x))
   case f: Failure => f
}

tryResult.recover(f) is

tryResult match {
  case Failure(e) if f.isDefinedAt(e) => Try(f(e))
  case other => other
}

There is no special magic. At every stage, you have values, not code in the scope of some try/catch. Putting those values in val will not change a thing. If you "debug by hand", you will notice that you get a Success object, then at some point, one of your map may fail and return a Failure, the following ones will keep being failures, and then when you pass the last one to recover, it may go back to Success. Settings vals in between cannot make any difference.

Upvotes: 1

Related Questions