What advantages does scala.util.Try have over try..catch?
Asked Answered
N

1

9

Searching online for the answer gives two prominent posts (Codacy's and Daniel Westheide's), and both give the same answer as Scala's official documentation for Try:

An important property of Try shown in the above example is its ability to pipeline, or chain, operations, catching exceptions along the way.

The example referenced above is:

import scala.io.StdIn
import scala.util.{Try, Success, Failure}

def divide: Try[Int] = {
  val dividend = Try(StdIn.readLine("Enter an Int that you'd like to divide:\n").toInt)
  val divisor = Try(StdIn.readLine("Enter an Int that you'd like to divide by:\n").toInt)
  val problem = dividend.flatMap(x => divisor.map(y => x/y))
  problem match {
    case Success(v) =>
      println("Result of " + dividend.get + "/"+ divisor.get +" is: " + v)
      Success(v)
    case Failure(e) =>
      println("You must've divided by zero or entered something that's not an Int. Try again!")
      println("Info from the exception: " + e.getMessage)
      divide
  }
}

But I can just as easily pipeline operations using the conventional try block:

def divideConventional: Int = try {
  val dividend = StdIn.readLine("Enter an Int that you'd like to divide:\n").toInt
  val divisor = StdIn.readLine("Enter an Int that you'd like to divide by:\n").toInt
  val problem = dividend / divisor
  println("Result of " + dividend + "/"+ divisor +" is: " + problem)
  problem
} catch {
  case (e: Throwable) =>
    println("You must've divided by zero or entered something that's not an Int. Try again!")
    println("Info from the exception: " + e.getMessage)
    divideConventional
}

(Note: divide and divideConventional differ slightly in behavior in that the latter errors out at the first sign of trouble, but that's about it. Try entering "10a" as input to dividend to see what I mean.)

I'm trying to see scala.util.Try's pipelining advantage, but to me it seems the two methods are equal. What am I missing?

Numerable answered 28/11, 2016 at 5:4 Comment(3)
Note that Try(...) only catches non-fatal exceptions, so it's equivalent to `try { ... } catch { case NonFatal(e) => ... }Hemolysis
Note that Try is part of the type system. Any code that handles a value of type Try "knows" that it might be one or the other. A routine that might throw can wrap it in a Try and pass it off as someone else's problem. Without the Try there's no easy way to communicate, "Hey! Someone else needs to handle this."Drama
Daniel Westheide says in his post that "if you need to deal with an exception thrown by an Actor that is executed on some other thread, you obviously cannot do that by catching that exception." I think concurrency issue is also important.Invitation
T
8

I think you're having difficulty seeing the composition abilities of Try[T] because you're handling exceptions locally in both cases. What if you wanted to compose divideConventional with an additional operation?

We'd having some like:

def weNeedAnInt(i: Int) = i + 42

Then we'd have something like:

weNeedAnInt(divideConventional())

But let's say you want to max out the number of retries you allow the user to input (which is usually what you have in real life scenarios, where you can't re-enter a method forever? We'd have to additionally wrap the invocation of weNeedAnInt itself with a try-catch:

try {
  weNeedAnInt(divideConventional())
} catch {
   case NonFatal(e) => // Handle?
}

But if we used divide, and let's say it didn't handle exceptions locally and propogated the internal exception outwards:

def yetMoreIntsNeeded(i: Int) = i + 64

val result = divide.map(weNeedAnInt).map(yetMoreIntsNeeded) match {
  case Failure(e) => -1
  case Success(myInt) => myInt
}

println(s"Final output was: $result")

Is this not simpler? Perhaps, I think this has some subjectivity to the answer, I find it cleaner. Imagine we had a long pipeline of such operations, we can compose each Try[T] to the next, and only worry about the problems when once the pipeline completes.

Tound answered 28/11, 2016 at 7:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.