[Grace-core] Exceptions design

Michael Homer mwh at ecs.vuw.ac.nz
Sat Aug 25 16:14:35 PDT 2012


Hi,
>From discussions in the last videoconference and afterwards, a basic
idea of how Grace exceptions might work emerged. This is my vision of
it, but others may have seen it differently.

There is a predefined object "Exception". It supports two methods: raise
and refine.

The refine method returns a distinguished kind of exception:
  def MyException = Exception.refine "MyException"
Exceptions so refined also support the refine method:
  def MySubException = MyException.refine "MySubException"
The provided string is used to distinguish exceptions and as a printable
description of the error.

These values have the same type as the predefined object, and so support
raise as well:
  MyException.raise "an error occurred"
The raise method creates a new "exception packet" and jumps up the stack
to the exception handler, passing it that packet as payload. The raise
method is the only way of triggering exception-raising behaviour, and is
built in to the top-level Exception object, so all exceptions must be
derived from that hierarchy even though they might have the same type
through other means.

Exceptions may be handled:
  catch {
    MySubException.raise "an error occurred"
  } case { e : MyException -> print "Your exception: {e.message}." }

This catch-case construct parallels the match-case construct. The first
block is applied, and if an exception is raised the case block for that
exception is applied. If no case handles the exception, it continues to
bubble up the stack until it is handled.

The objects created from refining exceptions support the pattern
protocol, matching the exception packets created from them. Multiple
exceptions may share the same case block using the | combinator, but the
& combinator does not make sense here.

Exceptions are identified by the string used to create them.  Refinement
creates a hierarchy of exceptions: in this example, as MySubException is
refined from MyException, the exception packet will be matched by the
MyException pattern as well as by MySubException and Exception.

Exception packets may carry additional payload data of their own. They
have a field data : Any which is populated by the raiseWith method:
  MySubException.raiseWith("some message", obj)
No semantics are attached to the data field; it is simply preserved for
exception handler code to use for whatever purpose.

Exception packets will also have other data automatically associated
with them for error reporting, such as backtraces. All exception packets
would have the same type, as would all exceptions.

catch-case also supports a finally block:
  catch {
    processInput
  } case { e : MyException -> print "Your exception: {e.message}." }
  } case { x : SomeOtherException -> ... }
  } finally {
    // Close file handles, unwind state changes, etc
  }
The finally block is applied regardless of whether an exception was
raised, a non-local return occurred, or the block terminated normally.
The exact time that the block is applied is unspecified in the case of
an exception. If the program terminates in the main block or during an
exception handler the block may not be evaluated, if it is possible to
do that. The finally block would not have access to any names declared
in the main block.

An alternative to identifying exceptions by the String name given to
them on creation is to identify the object returned from each request to
"refine" individually (by incrementing a counter, perhaps). In this case
it would not be possible to create a new exception behaving in the same
way as an exception in library code, or to catch a particular exception
that was not exposed in the public interface of the library. The name
string would still be required for identifying the exception to the
user, but would not have any behavioural effect.

I'm looking for comments or clarifications of all that before I
implement something like it.
-Michael


More information about the Grace-core mailing list