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 = {
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!