4

Below is the definition of flatMap taken from scala.util.Success.

final case class Success[+T](value: T) extends Try[T] {
  def flatMap[U](f: T => Try[U]): Try[U] =
    try f(value)
    catch {
      case NonFatal(e) => Failure(e)
    }
}

Here the f function takes a value of type T and and does a possibly faulty operation. What I don't understand is why do we need to wrap application of f with try-catch. Here is the apply method of companion object of Try:

object Try {
  def apply[T](r: => T): Try[T] =
    try Success(r) catch {
    case NonFatal(e) => Failure(e)
  }
}

As you can see, the operation takes place in a Try application body already covered with try-catch, and will not throw, but rather store the thrown exception safely. So it appears to me that it should be safe (since no exception will be thrown) to call whatever f (the one passed to flatMap) is.

My question is why flatMap wraps application again with a try-catch, how is this necessary?

P.S: Here is a relevant question, but does not answer mine: Scala: how to understand the flatMap method of Try?

ciuncan
  • 1,062
  • 2
  • 11
  • 25

2 Answers2

4

If I understand your question correctly, you are wondering why you need the (potentially) double try/catch, well f could throw an exception before returning the Try:

scala> import scala.util.Try
import scala.util.Try

scala>   val someTry = Try(1)
someTry: scala.util.Try[Int] = Success(1)

scala>   someTry.flatMap(theInt => {
     |     throw new Exception("")
     |     Try(2)
     |   })
res1: scala.util.Try[Int] = Failure(java.lang.Exception: )

What would have happened here without the try/catch in the flatMap is that the exception would have escalated, which defeats the whole point of having a Try in the first place.

Ende Neu
  • 15,581
  • 5
  • 57
  • 68
  • I kind of anticipated that might be reason but, is this not an ill value for f? Can you give me a more concrete example where a function returning `Try` can rightfully also throw before returning a `Try` instance? – ciuncan Feb 16 '15 at 13:32
  • Yes my example is extreme, but think about the fact that you can have all sort of operations inside a `flatMap`, the most dangerous ones, mathematical, I/O, database and so on. – Ende Neu Feb 16 '15 at 13:38
  • I imagined they would be wrapped in a Try, too. Am I thinking too ideally about this? I guess implementation tries to cover these kinds of misuse, right? – ciuncan Feb 16 '15 at 13:42
  • 1
    Yes, think about the fact that you can't constrain `f` to be something 'safe', I can't really think of a good example right now because to be honest it seems hard to misuse, but it could happen and for the very nature `Try` has it must be robust. – Ende Neu Feb 16 '15 at 13:47
  • I see. Thank you for your answers. I guess I'll wait a bit to see if any other answer comes, for the sake of completeness, then I'll mark this as an answer if no better comes. – ciuncan Feb 16 '15 at 13:52
1

The problem is in the assumption that when a function's return type is Try[T] it cannot throw any exceptions.

You have quoted definition of object Try's apply method to argue that it takes care of all non-fatal exceptions. But how do we know that the user defined function has used it.

def test1(x: Int): Try[Int] = Try { if (x > 0) x 
                                    else throw new Exception("failed") }

// ideally all functions should be like test1.

def test2(x: Int): Try[Int] = if (x > 0) Success(x)
                              else Failure(new Exception("Failed"))

def test3(x: Int): Try[Int] = if (x > 0) Success(x) 
                              else throw new Exception("Failed") // no Try.apply here.

// this compiles fine.
// since the type of else branch is Nothing, the overall return type is 'Try union Nothing' which is Try.

For same reason, scala.concurrent.Future also uses try-catch in its flatMap: to provide chaining of operations with bullet-proof exception handling.

It also shows that for scala compiler, Try or Future are just normal types.

Shyamendra Solanki
  • 8,751
  • 2
  • 31
  • 25
  • So the reason seems to be to have a bullet-proof implementation, it ensures the chaining will never fail with unanticipated exception throws. Thanks. – ciuncan Feb 16 '15 at 15:05