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

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

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

Example above:

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 not an Int. Try again!") println("Info from the exception: " + e.getMessage) divide } } 

But I can just as easily perform operations using a regular 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 not an Int. Try again!") println("Info from the exception: " + e.getMessage) divideConventional } 

(Note: divide and divideConventional slightly different in behavior, since the latter fail at the first sign of a problem, but more on that. Try entering โ€œ10aโ€ as the input to dividend to see what I mean.)

I try to see the advantage of pipelining scala.util.Try , but it seems to me that both methods are equal. What am I missing?

+5
source share
1 answer

I think you have difficulty with Try[T] compositional capabilities because you handle exceptions locally in both cases. What if you want to create a divideConventional with an extra operation?

We will have these:

 def weNeedAnInt(i: Int) = i + 42 

Then we will have something like:

 weNeedAnInt(divideConventional()) 

But let me say that you want to maximize the number of retries that you allow the user to enter (which usually happens in real life scenarios when you cannot re-enter the method forever? Additionally end the call to weNeedAnInt yourself with a try-catch :

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

But if we used divide and suppose that it did not handle exceptions locally and propagate the internal exception to the outside:

 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") 

Isn't that easier? Perhaps I think this has some subjectivity in the answer, I find it more pure. Imagine that we had a long pipeline of such operations, we can compose each Try[T] to the next and only worry about problems when the pipeline ends.

+5
source

Source: https://habr.com/ru/post/1260427/


All Articles