Appendix C. Experimental Features

Table of Contents

C.1. Exceptions in Orc
C.1.1. Catching Java exceptions
C.1.2. Catching Orc Engine Errors
C.1.3. Exceptions in Practice
C.1.4. Typechecking Exceptions

C.1. Exceptions in Orc

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
-}

C.1.1. Catching Java exceptions

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

...

C.1.2. Catching Orc Engine Errors

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.

  • ArgumentTypeMismatchException
  • ArityMismatchException
  • MessageNotUnderstoodException

{- Detecting runtime errors using exceptions -}
class ArgumentTypeMismatchException = orc.error.runtime.ArgumentTypeMismatchException

try(
  if 3 then "foo"
) catch (ArgumentTypeMismatchException(e)) "error"

C.1.3. Exceptions in Practice

Exceptions add two important capabilities to the Orc language. First, they allow Java exceptions thrown by Java based sites, as well the Orc engine itself, to be caught, allowing previously undetectable errors to be handled by the programmer. Additionally, they simplify error handling significantly, by removing error handling code from the body of the program. This is especially helpful when propagating error information across function calls or various parts of the program. In the examples below, let 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"

C.1.4. Typechecking Exceptions

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.