Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala 2.8 control Exception - what is the point?

Tags:

In the upcoming scala 2.8, a util.control package has been added which includes a break library and a construct for handling exceptions so that code which looks like:

type NFE = NumberFormatException val arg = "1" val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None } 

Can be replaced with code like:

import util.control.Exception._ val maybeInt = catching(classOf[NFE]) opt arg.toInt 

My question is why? What does this add to the language other than providing another (and radically different) way to express the same thing? Is there anything which can be expressed using the new control but not via try-catch? Is it a DSL supposed to make exception-handling in Scala look like some other language (and if so, which one)?

like image 653
oxbow_lakes Avatar asked Oct 29 '09 16:10

oxbow_lakes


2 Answers

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.

like image 78
Daniel C. Sobral Avatar answered Sep 27 '22 22:09

Daniel C. Sobral


As the guy who wrote it, the reasons are composition and encapsulation. The scala compiler (and I am betting most decent sized source bases) is littered with places which swallow all exceptions -- you can see these with -Ywarn-catches -- because the programmer is too lazy to enumerate the relevant ones, which is understandable because it's annoying. By making it possible to define, re-use, and compose catch and finally blocks independently of the try logic, I hoped to lower the barrier to writing sensible blocks.

And, it's not exactly finished, and I'm working on a million other areas too. If you look through the actors code you can see examples of huge cut-and-pasted multiply-nested try/catch/finally blocks. I was/am unwilling to settle for try { catch { try { catch { try { catch ...

My eventual plan is to have catch take an actual PartialFunction rather than require a literal list of case statements, which presents a whole new level of opportunity, i.e. try foo() catch getPF()

like image 44
psp Avatar answered Sep 28 '22 00:09

psp