Reputation: 2328
I understand there is difference between Try/Success/Failure vs. Try Catch. The official document also has a nice example:
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 <------------ Here!
}
}
However, I have difficulty in understanding
An important property of Try shown in the above example is its ability to pipeline, or chain, operations, catching exceptions along the way. The flatMap and map combinators in the above example each essentially pass off either their successfully completed value, wrapped in the Success type for it to be further operated upon by the next combinator in the chain, or the exception wrapped in the Failure type usually to be simply passed on down the chain. Combinators such as recover and recoverWith are designed to provide some type of default behavior in the case of failure.
Are there any examples to illustrate how does it make easier to "pipeline, chain, operations...."? Any examples to illustrate why we do not have the same benefits of try-catch?
Also, why do we return divide
in Failure
? Otherwise, we will have type mismatch
? Is this the only reason?
Upvotes: 0
Views: 4644
Reputation: 3863
Try[A]
This is an Algebraic Data Type (ADT) composed of 2 cases: Success[A]
and Failure[A]
. This algebraic structure defines a lot of operations like map
, flatMap
and others. The fact that Try
has a map
and a flatMap
plus a constructor from A
to Try[A]
, makes this structure a Monad.
Monads are abstractions that allow us to express sequences of computations in a functional way. Let's see an example
def tryDivide(v1: Double, v2: Double): Try[Double] = Try(v1 / v2)
val tryDivisions = tryDivide(1.0, 2.0).flatMap {
res => tryDivide(res, 2)
}.flatMap {
res => tryDivide(res, 0)
}
as a result of this code we will get a Failure[A]
because the latest operation is dividing by zero. The fact that Try is a Monad allows us to rewrite this using a for comprehension like this
for {
v1 <- tryDivide(1.0, 2.0)
v2 <- tryDivide(v1, 2)
res <- tryDivide(v2, 0)
} yield res
try
On the other hand try
is just a language syntax that allows you to catch exceptions that might be thrown from your code, but it's not a algebraic structure with methods that you can execute.
Upvotes: 8
Reputation: 2392
Try[A]
represents a computation that, if it's succeeded is a value of type A
, otherwise something has gone wrong and is a Throwable
. It comes in two flavors: in a successful computation it is a value of type A
wrapped as an instance of Success[A]
. In a failure, it is an instance of Failure[A]
, which wraps a Throwable
exception.
One of the characteristics of Try
is that it lets you use high-order functions (like Option
, Either
and other monads) and chain operations in a very smart way.
Let's say you have this little program:
scala> def divide(x: Int, y: Int): Try[Int] =
if (y == 0) Try(throw new Exception("Error: division by zero!"))
else Try(x / y)
divide: (x: Int, y: Int)scala.util.Try[Int]
You get
scala> divide(3,2)
res30: scala.util.Try[Int] = Success(1)
and
scala> divide(3,0)
res31: scala.util.Try[Int] = Failure(java.lang.Exception: Error: division by zero!)
So you can use the ability of high-order functions to capture the failures in a elegant way likewise:
scala> divide(3,0).getOrElse(0)
res32: Int = 0
This is just an example, but think about if you were trying to perform a more complex computation that might fail and you had to capture this result and act in consequence.
Why won't you use try catchs
in Scala? It's not because you won't be able to do so, rather because it's not very functional. If you need to deal with an exception that occurs in another thread (let's say, by an actor), you can't catch that exception, you might want to pass a message to your current main thread stating that the computation has failed and you will figure out how to handle that.
Also, why do we return divide in Failure? Otherwise, we will have type mismatch? Is this the only reason?
It returns divide
in Failure
because it let's you to re-run the program if it failed, but it just a decision made by the programmer. You could just returned a Failure
wrapping some kind of error message.
Upvotes: 1
Reputation: 71
Firstly some background about flatMap(): This function refers in many ways to well known concept of monadic >= (bind) operator, if you want to know more in-depth about it I highly recommend to read the book "Learn You a Haskell for Great Good!".
But to put it simply using Try you can do things like that:
someTry.flatMap(//doSomething1).flatMap(//doSomething2)
and then do some match to obtain value or get error from first or the second function (note that any flatMap from error won't be computed and there error will stay the same) but with the old way you would do smth like this:
try{
//doSomething()
} catch {
...
}
try {
//domSomething2()
} catch {
...
}
Upvotes: 0