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 = {
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.
This is happening because in Scala unlike Java, try-catch are expressions. In your code, try block returns a String
and your catch block return type is Unit
(as you are just printing and returning nothing).
The type-inference hence takes the return type T
such that T >: Unit
and T >: String
. Hence T
is of type Any
. And because you have declared foo as def foo: String
. The compiler throws an error as it was expecting String but found Any.
What you can do is to return a default value under you catch block:
try{
in.readLine
}
catch {
case e: IOException => {
e.printStackTrace()
"error string"
}
}
finally{
in.close
}
This method returns a Unit
; you have to return something in the catch block; or I'd rather suggest to change the return type to e.g. Option
; or use Try
class.
What is obvious - you don't always have a string in this situation. In Java people tend to ignore the realities, but this is Scala, one can do better.
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!