Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Try ... catch ... finally return value

Tags:

scala

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.

like image 499
Alan Coromano Avatar asked Sep 08 '13 15:09

Alan Coromano


People also ask

How do I return a value on try catch?

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.

What happens if catch and finally both return value?

When catch and finally block both return value, method will ultimately return value returned by finally block irrespective of value returned by catch block.

Where do I return my try catch finally?

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.

Can finally return value?

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.


2 Answers

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.

like image 56
ToxicFrog Avatar answered Oct 21 '22 10:10

ToxicFrog


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!

like image 34
Alex Avatar answered Oct 21 '22 11:10

Alex