This question already has an answer here:
How come the following code prints Success(4) if i'm throwing an exception inside?
(1 answer)
Closed 7 years ago.
println(
Try(1)
.map(doOne)
.map(doTwo)
.recover {
// this catches only errors from doTwo
case e: Throwable => println("recovering from: " + e.getMessage)
}
)
def doOne(i: Int): Int = i + 1; throw new RuntimeException("failed in one")
def doTwo(i: Int): Int = i + 2
How can a single recover catch any errors from preceeding maps?
It would catch both error. Isn't your problem just that you forgot bracket in doOne?
That should be :
def doOne(i: Int): Int = { i + 1; throw new RuntimeException("failed in one")}
Otherwise, it just means
def doOne(i: Int) = i + 1
throw new RuntinmeException ...
The exception is thrown at the start of your program, completely outside Try.
Related
I am struggling to understand the for comprehension and exception handling in Scala.
If the first statement in a for comprehension fails , recover is not able to catch the exception.
Code where recover catches the exception successfully(Exception thrown in 2nd statement):
import scala.util.{Success, Try}
object ExceptionThrownIn2ndStatement {
def failTryUnit(x: Unit): Try[Int] = {
println(x)
val a = 1 / 0
new Success(a)
}
def main(args: Array[String]): Unit = {
(for {
var0 <- Try(println("Zeroth function"))
varFailure <- failTryUnit(var0) //exception thrown here
var1 <- Try(println("first function", varFailure))
} yield var1) recover { case e =>
println("Exception caught", e) //exception caught here
}
}
}
Output :
Zeroth function
()
(Exception caught,java.lang.ArithmeticException: / by zero)
Code where recover does NOT catch the exception successfully :
import scala.util.{Success, Try}
object ExceptionThrownIn1stStatement {
def failTryUnit(x: Unit): Try[Int] = {
println(x)
val a = 1 / 0
new Success(a)
}
def main(args: Array[String]): Unit = {
(for {
varFailure <- failTryUnit({}) //exception thrown here
var0 <- Try(println("zeroth function", varFailure))
var1 <- Try(println("first function", var0))
} yield var1) recover { case e =>
println("Exception caught",e) //Exception does not get caught here
}
}
}
Output:
()
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionThrownIn1stStatement$.failTryUnit(ExceptionThrownIn1stStatement.scala:6)
at ExceptionThrownIn1stStatement$.main(ExceptionThrownIn1stStatement.scala:12)
at ExceptionThrownIn1stStatement.main(ExceptionThrownIn1stStatement.scala)
Edit : I understand that this is not the way recover is supposed to be used.
I am just confused as to why this happens. Please help me understand this. I am new to Scala.
Here is a shorter example that demonstrates the same behavior:
Success(42).flatMap(x => { assert(false); Success(x + 58) })
vs.
{ assert(false); Success(42) }.flatMap(x => Success(x + 58))
The first one will return a Failure with a caught error. The second one will crash with an AssertionError.
The first returns a Failure because that's the semantics of Trys flatMap - it catches all exceptions that occur during the execution of the function passed to it.
The second one crashes immediately, because the very first statement is an assert(false), so you never get to the point where you construct a Try in the first place, the AssertionError is thrown before the first Success constructor is invoked. It wouldn't matter whether you append more recovers on it or not - no Try will ever be instantiated in this program.
Here is what you would have to do to catch the exception occurring during the very first calculation (42):
Try { assert(false); 42 }.flatMap(x => Success(x + 58))
In your code, that would be
def failTryUnit(x: Unit): Try[Int] = Try {
println(x)
1 / 0
}
In the code below I have two Play for Scala functions, the first one catches an exception (this works fine) and in the second one I'm trying to rewrite it using Try.
I have two problems with Try: (1) when the number is negative the method doesn't fail, (2) I need to wrap all the responses with Future.successful.
How to fix this code?
class Test extends Controller {
def test1 = Action.async { request =>
val future = isPositive(-1)
future.map { result =>
Ok("OK, it's positive")
}
.recover {
case e => Ok(e.getMessage)
}
}
def isPositive(i: Int) = Future {
if (i<0)
throw new Exception ( "Number is negative" )
else
i
}
def test2 = Action.async { request =>
isPositiveWithTry(-1) match {
case Success(s) => Future.successful(Ok("OK, it's positive (Try succeded)"))
case Failure(f) => Future.successful(Ok(f.getMessage + " (Try failed)"))
}
}
def isPositiveWithTry(i: Int) : Try[Future[Int]] = Try {
isPositive(i)
}
}
In isPositive method exceptions are already caught by Future
def isPositive(i: Int) = Future {
if (i<0)
throw new Exception ( "Number is negative" )
else
i
}
In the below code
def isPositiveWithTry(i: Int) : Try[Future[Int]] = Try {
isPositive(i)
}
isPositive already catches all expections and Try is always a success.
So, when i is negative. Exception raised are handled by future and try gets a success value, resultant Try is a success. So you get successful Try with a failed Future inside.
Understanding using Grenade example
Assume throwing the exception as blowing up a Grenade.
Assume Future and Try as two layers. When grenade is blasted inside the double layer of Try[Future] i.e Try is around Future and grenade is gone off in the Future.
Now Future withstands the blast and becomes a failed value. As Future already took the damage caused by the damage of exception(grenade). Try will be a success but the value inside the Try is a failed future value. That failed future value is nothing but the exception raised.
Try is redundant when you are using Future
You can refactor your code to below one
Get rid of isPositiveWithTry. This method is not needed.
def isPositive(i: Int) = Future {
if (i<0)
throw new Exception ( "Number is negative" )
else
i
}
def test2 = Action.async { request =>
isPositive(-1).flatMap { _ =>
Future.successful(Ok("OK, it's positive (Try succeded)"))
}.recoverWith {
case f: Throwable => Future.successful(Ok(f.getMessage + " (Try failed)"))
}
}
Again test2 can also be written as
def test2 = Action.async { request =>
isPositive(-1).map { _ =>
Ok("OK, it's positive (Try succeded)")
}.recover {
case f: Throwable => Ok(f.getMessage + " (Try failed)")
}
}
In case isPositive returns Try
def isPositive(i: Int) = Try {
if (i<0)
throw new Exception ( "Number is negative" )
else
i
}
Now test2 will look like
def test2 = Action.async { request =>
isPositive(-1) match {
case Success(s) => Future.successful(Ok("OK, it's positive (Try succeded)"))
case Failure(f) => Future.successful(Ok(f.getMessage + " (Try failed)"))
}
}
Couple points:
1) You need to rewrite your isPositive such that it does not surround itself via a Future. The Future is catching the exception.
def isPositive(i: Int) ={
if (i<0)
throw new Exception ( "Number is negative" )
else
i
}
2) If you have a Try and you want a Future, then you can use the method on the companion object of Future, Future.fromTry. That will take a Try and turn it into the correct state of a Future.
I think my question is related but not the same as this one here.
Let define my first class
case class NoteTaker() {
private var note: Seq[String] = Seq("\n")
override def toString: String = this.note.mkString("\n")
def add(newNote: String): Unit = note ++= Seq(newNote)
}
Now I have a trait
trait SilentLogger {
import scala.util.{ Failure, Success }
val notepad = NoteTaker()
def tryAndLog[X, Y](x: X, f: X => Y): Y = {
notepad.add("Run with this input: " + x.toString)
(try {
println("Before: " + notepad.toString)
val result = f(x)
println("After: " + notepad.toString)
notepad.add("Get result:-------------------------------\n" + result.toString)
println(notepad.toString)
Success(result)
} catch {
case e: Throwable => {
println(
"Exception occurs:" + "\n" +
notepad.toString + "\n" +
e.getMessage + "\n" +
e.getStackTrace.mkString("\n")
)
Failure(e)
}}).get
}
}
I intend to use this trait to mix in with any classes where I want to collect some notes and only print out the note when there is an exception. Otherwise, I maybe just save it to a log file somewhere.
I want the notepad to be created once and reused for each of the object. In fact, I don't mind if they share the same notepad. Therefore, I chose to use 'val' in my trait.
As an example, I then create a class
case class MyClass (myField : String) extends SilentLogger {
def makeAnother : MyClass = tryAndLog("makeAnother",(x: String) => {
notepad.add(x)
val result = this.copy(myField = this.myField + " addNewField " + x)
notepad.add(result.myField)
return result
})
}
And finally I try to create two objects as follow:
scala> val firstObject = MyClass("firstObject")
firstObject: MyClass = MyClass(firstObject)
scala> val secondObject = firstObject.makeAnother
Before:
Run with this input: makeAnother
Exception occurs:
Run with this input: makeAnother
makeAnother
firstObject addNewField makeAnother
null
secondObject: MyClass = MyClass(firstObject addNewField makeAnother)
I'm really confused here. Obviously an exception occurred. But the secondObject was created just fine? But the logging message get printed out on stdout with the error 'null'.
I think my question is whether my first and second objects are actually using the same notepad or separate? How are the initialisation and the scope of notepad defined here? Is there something wrong with the way I use 'Try'?
This is caused of anonymous function with explicitly return:
(x: String) => {
notepad.add(x)
val result = this.copy(myField = this.myField + " addNewField " + x)
notepad.add(result.myField)
return result
}
In Scala, when explicitly declare return in anonymous function, it will throw NonLocalReturnControl, this will skip the later code block execute, since you have catched the Throwable, so it also will go to your catch code block.
So maybe you can remove return directly to solve this issue.
object MyRealMainObj extends App {
println(
Try(1)
.map(doOne)
.map(doTwo)
)
def doOne(i: Int): Int = i + 1; throw new RuntimeException("failed in one")
def doTwo(i: Int): Int = i + 2
}
result:
Success(4)
Exception in thread "main" java.lang.RuntimeException: failed in one
at MyRealMainObj$.delayedEndpoint$MyRealMainObj$1(TestMainArgs.scala:16)
at MyRealMainObj$delayedInit$body.apply(TestMainArgs.scala:7)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:76)
at scala.App$$anonfun$main$1.apply(App.scala:76)
How come it printed Success(4) didn't the doOne was supposed to fail as i'm throwing exception there?
wrap the code inside curly braces like,
def doOne(i: Int): Int = {i + 1; throw new RuntimeException("failed in one")}
The throw new RuntimeException("failed in one") is not within the function it is a separate line inside main flow.
Starting from this answer:
Scala continuation and exception handling
I would like to know if there is a way to re-execute the ENTIRE try block (or ctry block in the example code) after the exception is handled.
What I mean is if the resume() operation could move the execution to the first statement of the try block instead of simply resuming the execution from the statement that raised the exception.
Thanks a lot!
Here's a silly example that I think will steer you toward an answer:
object Retry
object SO_7331489 extends App {
import scala.util.continuations._
var dividend: String = "ten"
var divisor: Int = 0
val quotient = reset {
shift { k: (Unit => Any) =>
def retry: Any =
k() match {
case Retry => retry
case x => x
}
retry
}
try {
dividend.toInt / divisor
} catch {
case e: java.lang.NumberFormatException =>
println("caught " + e.getClass.getName + "(" + e.getMessage + ")")
dividend = "10"
println("retrying with dividend = \"10\"")
Retry
case e: java.lang.ArithmeticException =>
println("caught " + e.getClass.getName + "(" + e.getMessage + ")")
divisor = 2
println("retrying with divisor = 2")
Retry
case t: Throwable =>
println("caught " + t.getClass.getName + "(" + t.getMessage + ")")
// Nothing left to retry, so don't return Retry
}
}
println("quotient: " + quotient)
}
The idea is to trigger the shift block before your try/catch block. Within the catch block, if you want to retry the whole thing, simply return the Retry object.
It might be nice to pull the entire shift block out into a simple function (e.g. retry), but I left it inline for clarity.
This code produces the following output:
[info] Running SO_7331489
caught java.lang.NumberFormatException(For input string: "ten")
retrying with dividend = "10"
caught java.lang.ArithmeticException(/ by zero)
retrying with divisor = 2
quotient: 5