Thursday, May 31, 2012

Exception Handling in Scala

For exception handling, both Scala and Java have similar try/catch/finally constructs.  In addition, exception handling in Scala can be done using the various member classes of the scala.util.control.Exception object, such as Catch, Try, Catcher, and Finally, in stead of the try/catch/finally construct. In this tutorial, we are going to see examples of both approaches.

try/catch/finally Construct

There is a small difference between Scala and Java's try/catch/finally constructs. In Java, one may have multiple catch clauses for a single try clause, each for a exception class (the Exception class itself or its subclass). In Scala, only one catch clause is needed, and multiple cases are used to handle multiple exception classes. Listing 1 below illustrate such exception handling in Scala.

Listing 1 - Example Using the try/catch/finally Construct

package exception.handling.examples

import scala.io.Source
import java.io.IOException
import java.io.FileNotFoundException

object TryClauseExample extends App {
    val fileName = "c:\\temp\\test.txt"
     
    val content =
    try {
        Source.fromFile(fileName).getLines.mkString("\n")
    } catch {
          case e: FileNotFoundException => throw new SystemError(e)
          case e: IOException => throw new SystemError(e)
    }
    
    println(content)
}

In Listing 1, the simple program reads a file from the file system, store the file content in a variable named content, and print content's value to the console. If a FileNotFoundException, or a more general IOException occurs, it wraps the exception into a custom excepton named SystemError (see Listing 2) and throws the SystemError exception. Since FileNotFoundException is not treated in any way different from the more general IOException, one case will be sufficient. We used two cases in the example just to show how to handle different Exception subclasses.

Listing 2 - SystemError

package exception.handling.examples

class SystemError(message: String, nestedException: Throwable) extends Exception(message, nestedException) {
    def this() = this("", null)
    
    def this(message: String) = this(message, null)
    
    def this(nestedException : Throwable) = this("", nestedException)
}

scala.util.control.Exception object and its Member Classes

It is not necessary to use the try/catch/finally construct to handle exceptions. The same thing can be done using various methods and nested classes of the singleton object scala.util.control.Exception. In this tutorial, we are just going to refer to this singleton object as the Exception singleton. The program in Listing 3 is equivalent to the program in Listing 1, but does not use the try/catch/finally construct.

Listing 3 - First Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content)
}

The most important thing about the Exception singleton is its (nested) member class Catch. Every object of this class captures the exception handling logic usually captured by the catch (and optionally the finally) part of a try/catch/finally statement. In general, such exception logic are about:
  1. What exception, e.g. FileNotFoundException and IOException, to catch
  2. What to do when catching each of those exceptions, e.g. wrapping the exception in another exception and throwing the later, 
  3. Optionally, what to do always no mater whether an exception occurred or not, i.e. the body of a finally clause in a try/catch/finally statement
The Exception singleton has a method whose header is

catching[T](exceptions: Class[_]*): Catch[T]

 

This method returns a Catch object. The method parameter exceptions are the exception classes to be caught by the Catch object. In our example (Line 11 of Listing 3), calling the catching method and passing to it two exception classes (i.e. FileNotFoundException and IOException) returns a Catch object that will catch FileNotFoundException and IOException.

The Catch class has a method withApply, whose header is

withApply[U](f: (Throwable) ⇒ U): Catch[U] 

 

Calling this method on a Catch object will return another Catch object that will execute the anonymous function passed as argument to the withApply method, when a target exception is caught. In our example (Line 11 of Listing 3), variable fileCatch references a Catch object that will catch FileNotFoundException and IOException, and will throw a custom exception - SystemError - that wraps the caught exception.

The Catch class has a method apply, whose header is

apply[U >: T](body: ⇒ U): U

 

Calling the apply method, passing a code block as argument to the method, will execute the code block, like the case of a try clause in a try/catch/finally statement. If that code block throw any exception targeted by the Catch object, the Catch object will catch the exception and execute another code block - the one passed to it via calling its withApply method.

This explains why the two programs in Listing 3 and Listing 1 are equivalent.

Listing 4 - Second Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch.opt{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content.getOrElse("The file cannot be found or read"))
}

Listing 4 is slightly different from Listing 5. In Line 13, instead of the apply method, the opt method, of the Catch object is called. The opt method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the opt method with the same code block as argument will return a Some object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the opt method with the same code block as argument will return None. The program in Listing 4 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.


Listing 5 - Third Example Using the Catch Class 

package exception.handling.examples

import scala.util.control.Exception.catching
import java.io.IOException
import java.io.FileNotFoundException
import scala.io.Source

object CatchClassExample1 extends App {
    val fileName = "c:\\temp\\test.txt"
        
    val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
    
    val content = fileCatch.either{Source.fromFile(fileName).getLines.mkString("\n")}
    
    println(content.right.getOrElse("The file cannot be found or read"))
}

Listing 5 is also slightly different from Listing 3. In Line 13, instead of the apply method, the either method, of the Catch object is called. The either method, like the apply method, takes a code block as parameter, and executes the code block. If calling the apply method with a certain code block as argument returns normally, i.e. not throwing exception, calling the either method with the same code block as argument will return a Right object wrapping that normal result; if calling the apply method with a certain code block as argument throws an exception, calling the either method with the same code block as argument will return a Left object wrapping the exception. The program in Listing 5 will print the file content to the console if the file can be found and read. If the file cannot be found or read, it will print string "The file cannot be found or read" to the console.

Conclusion
 The exception handling with the Catch class has two advantages comparing with the traditional try/catch/finally construct approach:
  1. reusing exception handling logic. That is "Don't Repeat Yourself". A Catch object captures a certain exception handling logic. One can call the apply (or opt, or either) on the same Catch object many times, each time with different argument - code block equivalent to the body of a try clause. In the case of the try/catch/finally construct approach, when there are multiple try/catch/finally statements only differ in the try clause, the catch and finally clause still has to be duplicated for each try clause.
  2. Supporting Monad, with the opt and either methods of the Catch class
 The exception handling with the Catch class also has two disadvantages comparing with the traditional try/catch/finally construct approach:
  1. Unfamiliar to a large body of programmers, while almost every programmer with basic knowledge of Object-Oriented programming is familiar with the try/catch/finally construct.
  2. Scattering the exception handling logic about what exceptions to catch, what to do when catch them, and the code that throws those exception into different location in source code.When the gaps between those locations are large, likely the logic will be more difficult to understand than other wise. 
The first disadvantage can be overcome with a little training. In general the second disadvantage cannot be easily overcome. Programmers need to be cautious and weight the costs over benefits while choosing one approach over the other.

1 comment:

JPK said...

good explanation..