There are two ways to think about exceptions. One way is to think of them as flow control: an exception changes the flow of execution of the program, making the execution jump from one place to another. A second way is to think of them as data: an exception is an information about the execution of the program, which can then be used as input to other parts of the program.
The try
/catch
paradigm used in C++ and Java is very much of the first kind(*).
If, however, if you prefer to deal with exceptions as data, then you'll have to resort to code such as the one shown. For the simple case, that's rather easy. However, when it comes to the functional style where composition is king, things start to get complicated. You either have to duplicate code all around, or you roll your own library to deal with it.
Therefore, in a language which purports to support both functional and OO style, one shouldn't be surprised to see library support for treating exceptions as data.
And note that there is oh-so-many other possibilities provided by Exception
to handle things. You can, for instance, chain catch handlers, much in the way that Lift chain partial functions to make it easy to delegate responsibility over the handling of web page requests.
Here is one example of what can be done, since automatic resource management is in vogue these days:
def arm[T <: java.io.Closeable,R](resource: T)(body: T => R)(handlers: Catch[R]):R = (
handlers
andFinally (ignoring(classOf[Any]) { resource.close() })
apply body(resource)
)
Which gives you a safe closing of the resource (note the use of ignoring), and still applies any catching logic you may want to use.
(*) Curiously, Forth's exception control, catch
&throw
, is a mix of them. The flow jumps from throw
to catch
, but then that information is treated as data.
EDIT
Ok, ok, I yield. I'll give an example. ONE example, and that's it! I hope this isn't too contrived, but there's no way around it. This kind of thing would be most useful in large frameworks, not in small samples.
At any rate, let's first define something to do with the resource. I decided on printing lines and returning the number of lines printed, and here is the code:
def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 0
var lineText = lnr.readLine()
while (null != lineText) {
lineNumber += 1
println("%4d: %s" format (lineNumber, lineText))
lineText = lnr.readLine()
}
lineNumber
} _
Here is the type of this function:
linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int
So, arm
received a generic Closeable, but I need a LineNumberReader, so when I call this function I need to pass that. What I return, however, is a function Catch[Int] => Int
, which means I need to pass two parameters to linePrinter
to get it to work. Let's come up with a Reader
, now:
val functionText = """def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 1
var lineText = lnr.readLine()
while (null != lineText) {
println("%4d: %s" format (lineNumber, lineText))
lineNumber += 1
lineText = lnr.readLine()
}
lineNumber
} _"""
val reader = new java.io.LineNumberReader(new java.io.StringReader(functionText))
So, now, let's use it. First, a simple example:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
1: def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
2: var lineNumber = 1
3: var lineText = lnr.readLine()
4: while (null != lineText) {
5: println("%4d: %s" format (lineNumber, lineText))
6: lineNumber += 1
7: lineText = lnr.readLine()
8: }
9: lineNumber
10: } _
res6: Int = 10
And if I try it again, I get this:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
java.io.IOException: Stream closed
Now suppose I want to return 0 if any exception happens. I can do it like this:
linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))
What's interesting here is that I completely decoupled the exception handling (the catch
part of try
/catch
) from the closing of the resource, which is done through finally
. Also, the error handling is a value I can pass on to the function. At the very least, it makes mocking of try
/catch
/finally
statements much easier. :-)
Also, I can combine multiple Catch
using the or
method, so that different layers of my code might choose to add different handlers for different exceptions. Which really is my main point, but I couldn't find an exception-rich interface (in the brief time I looked :).
I'll finish with a remark about the definition of arm
I gave. It is not a good one. Particularly, I can't use Catch
methods such as toEither
or toOption
to change the result from R
to something else, which seriously decreases the value of using Catch
in it. I'm not sure how to go about changing that, though.