I found that it's a shame that I can't return a return value from a such simple construction as try ... catch ... finally
def foo: String = {
val in = new BufferedReader(.....)
try {
// val in = new BufferedReader(.....) -- doesn't matter
in.readLine
}
catch {
case e: IOException => e.printStackTrace()
}
finally {
in.close()
}
}
This code doesn't compile. Is there any way to make compile expect using any libraries, high-level constructions, etc? I want to do that only by using the capacity of pure Scala as a programming language.
In a try-catch-finally block that has return statements, only the value from the finally block will be returned. When returning reference types, be aware of any updates being done on them in the finally block that could end up in unwanted results.
When catch and finally block both return value, method will ultimately return value returned by finally block irrespective of value returned by catch block.
In the preceding code, finally block overrides the value returned by try block. Therefore, this would return value 50 because the value returned by try has been overridden by finally block.
Yes you can write the return statement in a finally block and it will override the other return value. The output is always 2, as we are returning 2 from the finally block. Remember the finally always executes whether there is a exception or not.
In a scala try-catch-finally block, the finally
block is evaluated only for side effects; the value of the block as a whole is the value of the last expression in the try
(if no exception was thrown) or catch
(if one was).
If you look at the output from the compiler, you'll note that it's complaining about the contents of the catch
block, not the finally
:
$ scala test.scala
/tmp/test.scala:12: error: type mismatch;
found : Unit
required: String
case e: Exception => e.printStackTrace()
This is because Exception.printStackTrace()
returns Unit
, so the return type of the function would have to be String
if the try
succeeded and Unit
otherwise.
You can address this by having the catch
block evaluate to a String as well:
catch {
case e: IOException => {
e.printStackTrace()
e.toString()
}
}
Of course, this means there has to be some useful string value you can return even when an error occurs (perhaps ""
?); a more idiomatic approach might be to return an Option[String]
, with the try
block returning Some(in.readLine)
and the catch
block returning None
. In either case, though, the value of both the try
and catch
blocks must match the function signature. The finally
block's type is irrelevant.
For reference, here's a version that passes type checking and works:
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.IOException
def foo: String = {
val in = new BufferedReader(new InputStreamReader(System.in))
try {
in.readLine
}
catch {
case e: IOException => { e.printStackTrace(); e.toString() }
}
finally {
in.close()
}
}
System.out.println("Return value: " + foo)
in.close()
returns Unit, but that's ok because the value of the finally
block is ignored. The try
and catch
blocks both return String.
I think it would help to start with the Java conception of an exception. A Java method is basically a contract to do something (return a value or cause a side effect) if it is called. The method makes certain assumptions (for example, that the operating system will cooperate with a request to read a file). Sometimes, if these conditions are not met, it will return a null value, and sometimes it will stop execution completely and "throw an exception".
This can be a problem, because the contract of a Java method is not always clear. A Java method that declares a return type of String really has three outcomes: a String value, null, or an exception. One problem with exceptions is that they halt execution of the code, possibly masking other problems further down in the method or possibly failing to close resources that were opened (which is the reason for try, catch, finally
)
Scala seeks clarity regarding the return type. One way to do this would be to collect all the exceptions that occur in the method and then pass that list of exceptions as a return value. But then, we need to have a return type that says "We're going to return something, or we might return nothing" (scala.Option), or perhaps, "We're going to return Either the expected answer or we'll return information about why the expected answer is not being returned" (scala.util.Either), or perhaps, "We're going to Try to do a risky operation, which might result in Success or Failure." (scala.util.Try)
Scala deals with the possibility of a null value with Option. Option is class that has two subclasses: None
and Some
, which is a container that holds exactly one element. For example:
val contacts = Map("mark" -> 1235551212, "john" -> 2345551212, "william" -> 3455551212)
val phoneOfJohn: Option[Int] = contacts.get("john")
val phoneOfAlex: Option[Int] = contacts.get("alex")
phoneOfJohn match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call John, can't find him in contacts")
}
phoneOfAlex match {
case Some(number) => callPhone(number)
case None => Logger.debug("unable to call Alex, can't find him in contacts")
}
This codes make a phone call to John, and it will log the fact that it was unable call Alex because it couldn't find his phone number in the phone book. But an Option
doesn't provide information about why no value was returned. If we want to collect those reasons, we can use an Either
. The Either
has two subclasses: A Left
could store all of the exceptions that were collected in the process of doing the "risky operation" while a Right
would be similar to a Some
and contain the expected value.
Doing a fold operation on an Either to convert it to a Left or a Right is somewhat counter-intuitive, and thus we come to scala.util.Try.
@marius, a scala developer at Twitter, wrote a very good post about the rationale for adopting scala.util.Try. I think this is what you're looking for.
The essence of scala.util.Try is that a risky action can result in Success
or Failure
. Before scala.util.Try, developers would use an Option or Either. Here's what it would look like if you did a buffered reader from a file:
import scala.util.{Try, Failure, Success}
def foo(fileName: String): Try[String] = Try {
scala.io.Source.fromFile(fileName).bufferedReader().readLine()
}
def bar(file: String): Unit = foo(file) match {
case Success(answer) => Logger.info(s"$file says the answer is $answer")
case Failure(e) => Logger.error(s"couldn't get answer, errors: ${e.getStackTrace}")
}
bar("answer.txt") \\ will log the answer or a stack trace
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With