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

允我心安 提交于 2019-11-30 04:26:26

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/40837493/what-advantages-does-scala-util-try-have-over-try-catch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!