Search code examples
scalaerror-handlingtry-catchfor-comprehension

For comprehension Try monad with unhandled Exception


I am trying to learn how to use Try with for comprehensions in scala.

In the below sample code(result1),

if the last statement in for comprehension throws an unhandled exception, Code doesn't break and an Try[Int] is returned.

But, if the sequence of statements is changed in for comprehension (result2). A runtime exception is thrown.

package simpleTryExample

import scala.util.Try


object SimpleTryExample {

  def main(args: Array[String]): Unit = {

    val result1 = for {
      a <- strToInt("3")
      b <- strToInt("a")
    } yield (a + b)

    println("result1 is " + result1)

    val result2 = for {
      a <- strToInt("a")
      b <- strToInt("3")
    } yield (a + b)

    println("result2 is " + result2)

  }

  def strToInt(s: String): Try[Int] = {
    s match {
      case "a" =>
        println("input is a")
        throw new RuntimeException("a not allowed")
      case _ => println("other then a")
    }
    Try(s.toInt)
  }
}

Output :-

other then a
input is a
Exception in thread "main" java.lang.RuntimeException: a not allowed
    at simpleTryExample.SimpleTryExample$.strToInt(SimpleTryExample.scala:30)
    at simpleTryExample.SimpleTryExample$.main(SimpleTryExample.scala:18)
    at simpleTryExample.SimpleTryExample.main(SimpleTryExample.scala)
result1 is Failure(java.lang.RuntimeException: a not allowed)
input is a

I was expecting result2 to be a Try[Int] type. What i am doing wrong here ..?


Solution

  • The problem is that in your second example, you're throwing the exception before entering the Try[T] context.

    In your first example, when running strToInt("a"), you're already in the context of a Try[T], since the code is desugered to:

    strToInt("3").flatMap(_ => strToInt("a"))
    

    Since the first invocation of strToInt is successful, everything executed after it inside the for comprehension is in the context of a Try. But, in the second example, we have the opposite:

    strToInt("a").flatMap(_ => strToInt("3"))
    

    strToInt("a") will throw a RuntimeException since we're still not in a Try context, it is only applied when you try to parse s to an Int.

    To avoid this altogether, move the try higher up before the pattern match:

    def strToInt(s: String): Try[Int] = {
      Try {
        s match {
          case "a" =>
            println("input is a")
            throw new RuntimeException("a not allowed")
          case _ => println("other then a")
        }
        s.toInt
      }
    }
    

    And now we get:

    other then a
    input is a
    result1 is Failure(java.lang.RuntimeException: a not allowed)
    input is a
    result2 is Failure(java.lang.RuntimeException: a not allowed)