Table of Contents
We have added an experimental implementation of exceptions to the Orc language,
which allows exceptions to be raised, caught, and handled. Exceptions
are currently off by default, and can be enabled by adding the -exceptions flag
on the command line. It should also be noted that exception handling in Orc are currently experimental, and may be
removed in a future release.
Exceptions introduce three new keywords to the Orc syntax:
throw, try, and catch, which are defined as follows:
throw E: for each publication x by expression E, raise an exception with value x.
try E: Defines E to be a block, possibly containing throw expressions.
catch ( P ) E: Given pattern P and expression E, any exception
with value x that matches pattern P causes execution of E under the binding of P to x.
For each corresponding try expression, one or more catch statements are defined. Catch
statements are executed in order, and the first to match is the statement executed.
Examples of throw expressions:
throw 3 throw "error" throw (3 | 4) {- throws two exceptions -} throw stop {- throws no exceptions -}Below we combine both exception raising and handling:
{- simple exception expressions -} try(0 | throw 1 | throw 2) catch (1) "one" catch (x) x catch (2) "two" {- Output: 0, "one", 2 -}The value
0 is published by the expression directly, while "one" is
published by catch (1) "one", and 2 is published by catch(x) x.
The handler catch (2) "two" is not executed.
Exception handlers can also be nested; if the immediately enclosing
try/catch block has no handler that matches the thrown exception, the
Orc interpreter attempts to match against the next enclosing handler. If
no handler matches the exception (or no enclosing handler exists) the exception
is dropped (for example, it is treated as a stop)
and an error is reported to the interpreter.
{- a contrived nested handler example -} try( try(throw 3) catch (1) "one" ) catch (x) x
When combining exceptions with the combinators, i.e., the pruning or
otherwise combinators, exceptions are silent, that is, they do not publish any value.
For example, in the following expression, both 0 and
1 are published; throw 0 is silent, not publishing
any value, causing the otherwise combinator to also execute its alternate expression:
{- the throw expression is treated as a silent by the combinators -} try (throw 0; 1) catch(x) x {- output: 0 1 -}
Similarly, exceptions thrown within a pruning combinator do not terminate
the expression (since the throw is treated as a silent), and throwing a silent expression
(i.e., throw stop) results in no exception being thrown; the entire
expression functions as a stop. Unlike other languages, an uncaught exception
only kills the process that threw it, and the other processes in the system
will continue running. In the example below, the entire expression publishes no value
(because the expression inside the pruning combinator does not publish). Instead,
the exception thrown is uncaught and results in a runtime error:
x <x< throw 2 {- output: Error: uncaught exception: Backtrace: OrcJava/examples/uncaught.orc:1:8-15: x <x< throw 2 -}
Orc also allows Java exceptions thrown by sites
to be matched against by Orc exception
handlers. This is especially useful when calling Java code directly,
which might throw exceptions in cases of failure, seen here in this
short example. Attemping to create a FileInputSteam with
a nonexistent file path returns an error by throwing a FileNotFoundException.
By pattern matching for a FileNotFoundException, the
error can be detected and handled by the Orc program.
{- catching exceptions thrown by Java sites via pattern matching -} class FileInputStream = java.io.FileInputStream class FileNotFoundException = java.io.FileNotFoundException ... try( FileInputStream("non/existent/file/path") ) catch (FileNotFoundException(e)) e ...
This functionality also allows certain Orc runtime errors to be caught. Here are a few examples of runtime errors which might occur, and a full list can be found in the Javadoc.
ArgumentTypeMismatchExceptionArityMismatchExceptionMessageNotUnderstoodException
{- Detecting runtime errors using exceptions -} class ArgumentTypeMismatchException = orc.error.runtime.ArgumentTypeMismatchException try( if 3 then "foo" ) catch (ArgumentTypeMismatchException(e)) "error"
request be
a function that will return a single value eventually:
{- error handling without exceptions: -} def makeRequest() = let(request() >x> (true, x) | Rtimer(1000) >> (false, "timeout")) def call() = makeRequest() >(success, result)> if success then result else "failed"Compare this to using exceptions, where error handling is made explicit and the program as a whole is simplified. (Note the improvement would be more marked if a deeper layer of function nesting was used.)
{- error handling with exceptions -} def makeRequest() = request() | Rtimer(1000) >> throw("timeout") def call() = try( makeRequest() ) catch (_) "failed"
The Orc typechecker currently provides only partial support for the typechecking of exceptions.
The expression throw E always has type Bot. E is typechecked
to ensure that it does indeed have some type, but that type is ignored, since a throw expression
will raise exceptions rather than publish values.
In the expression try E catch(P) F, the type of the
whole expression is the type of E. The type of the handler body F must be the same as, or
some subtype of, the type of E.
The expression F is checked without information about the
type of the exception value bound by P; the typechecker uses the type Bot.
This is a hole in the typechecker; it is possible to write an exception handler which uses
its argument inappropriately and generates a runtime type error. For the moment, it is the
programmer's responsibility to ensure that a handler uses its argument appropriately; we are
currently investigating possible solutions to this problem.
In practice this weakness is rarely a problem, because the common use case for catch
handler arguments is to match against a Java exception class:
class FileNotFoundException = java.io.FileNotFoundException try ... catch (FileNotFoundException(e)) ...Since this effectively performs a cast, the variable
e is given the appropriate type
FileNotFoundException, and the typechecker behaves correctly in this case.