[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

a safer and more efficient exception system



This is a rearrangement of Kelsey's proposal, dividing it into
three distinct systems: an exception system, a condition data
type, and a restart system.  The basic idea, as formulated by
Kelsey, is:

> WITH-HANDLER could take a condition type as an additional argument
> and only invoke the handler for conditions that match the type.
> Presumably this is what almost all handlers will be doing anyway.

Furthermore I have separated exceptions from conditions, and
have moved multiple inheritance from conditions to exceptions.

The exception system and restart system are independently useful.
A condition is just a message they use to communicate.  Conditions
serve no other purpose.

Compared to Kelsey's proposal, this rearrangement is

  1.  not quite as general.  There are a couple of things you
      can do with Kelsey's system that you can't do in this
      system.

  2.  safer.  One of the things you can't do is to wedge the
      entire exception system with just one buggy exception
      handler.  System exceptions can use the exception system
      without being sabotaged by bugs in non-system code.

  3.  more efficient.  Signalling and returning from an exception
      might involve about ten RISC instructions in some
      implementations.

--------
The exception system.

Exceptions form a directed acyclic graph (partial order).
It is sometimes convenient to speak of an exception as a
class of exceptions, because each exception represents itself
together with all the exceptions that lie below it in this
partial order.

(exception<? <exception1> <exception2>)

  Returns a boolean indicating whether the class of exceptions
  represented by <exception1> is a subclass of the exceptions
  represented by <exception2>.

(make-exception <id> [<exception>]*)

  Returns a new exception.
  <id> is a string used only when printing the exception.
  The new exception becomes a subclass of [<exception>]*,
  so all exception handlers for [<exception>]*
  must be able to handle the new exception.

(call-with-handler <exception> <handler> <thunk>)

  Calls <thunk> with <exception> (and all its subclasses)
  bound to <handler>.

(signal <exception> [<datum>]*)

  Calls the current handler for <exception>, passing <exception>
  as the first argument and [<datum>]* as the remaining arguments.

It would be foolish to deal with too many details until we
are nearing agreement on the framework, but I would expect
the R5RS to describe the part of the exception hierarchy
that deals with exceptions that arise within the core language
and the predefined R5RS procedures.  The R5RS would also
describe the arguments passed for each <exception> in that
part of the hierarchy.

A typical implementation would have many more exceptions.
How many of these system exceptions are made accessible to
Scheme programs would be left up to each implementation.
Scheme programs would be free to define their own exceptions,
ex nihilo, by specializing the R5RS exceptions, or by
specializing the public system exceptions.

--------
The condition system.

Conditions are the sentences of a declarative language that
allows exception handlers to communicate with the restart
system.

Kelsey's proposal for conditions could be used as is, but there
would then be no necessary correspondence between the exception
hierarchy and the condition hierarchy.  Those two hierarchies
don't have to coincide, by the way:  Conditions could be
orthogonal to exceptions, and everything would still work.

To maintain a correspondence between Kelsey's proposal and
this one, I'm going to require each condition to contain an
exception.  This can be viewed as imposing the exception
hierarchy on conditions, but I don't view it that way.

Although I earlier disagreed with Kent Pitman when he said
the purpose of an exception system is to allow parts of a
program that know little or nothing about each other to
communicate, I now believe he was talking about the purpose
of a system that I would characterize as a "condition" system,
not an "exception" system.  If so, then our disagreement was
a matter of nomenclature, because I certainly agree that this
is the purpose of a condition system.

Toward that end, I propose to make conditions more transparent
(reflective) than in Kelsey's proposal.

(make-condition <exception> [<field-name> <datum>]*)

  <exception> is an exception, each <field-name> is a symbol,
  and each <datum> is an arbitrary value.  MAKE-CONDITION
  returns a condition.

(condition-exception <condition>)

  returns the <exception> that was passed to MAKE-CONDITION
  when <condition> was created.

(condition-fields <condition>)

  returns a list of symbols: the field names for the <condition>.

(condition-ref <condition> <field-name>)

  returns the <datum> associated with the symbol <field-name>
  within the <condition>.  If the <field-name> is not defined
  for the <condition>, CONDITION-REF just returns #F.  This
  creates no real ambiguity because CONDITION-FIELDS can be
  used to distinguish between fields whose values is #F and
  fields that don't exist.

--------
The restart system.

The restart system can be exactly the same as in Kelsey's proposal.
I suspect that it could be simplified a bit, however.

In my opinion, the list returned by FIND-RESTARTS should be in
order from the most recently bound active restart to the least
recently bound.

I'd also like to have a way to associate textual messages with
strings in different languages, but that facility is needed
independently of the restart system and should be proposed
separately.  I mention this here because the "natural language"
to be used by a restart's discloser depends upon it.

--------
Example.

; A tiny piece of the R5RS exception hierarchy might look
; like this:
;
; R5RS-EXCEPTION
;   R5RS-DOMAIN-EXCEPTION
;     ARG1-HAS-WRONG-TYPE-EXCEPTION
;
; This implies that an ARG1-HAS-WRONG-TYPE-EXCEPTION can be handled
; by a handler for a R5RS-DOMAIN-EXCEPTION, which can in turn be
; handled by a handler for a R5RS-EXCEPTION.
;
; A particular implementation of Scheme might implement these
; exceptions, the associated condition types, and their default
; exception handlers as follows.

; Public.

(define r5rs-exception
  (make-exception "r5rs"))
(define r5rs-domain-exception
  (make-exception "domain" r5rs-exception))
(define arg1-has-wrong-type-exception
  (make-exception "arg1-type" r5rs-domain-exception))

(define (choose-restart restarts)
  (if (null? restarts)
      (throw-to-new-read-eval-print-loop)
      (do ((i 0 (+ i 1))
           (restarts (reverse restarts) (cdr restarts)))
          ((null? restarts))
          (write i (interactive-output-port))
          (disclose-restart (car restarts)
                            (interactive-output-port))
          (newline (interactive-output-port)))
      (letrec ((read-n (lambda ()
                         (display "Choose your poison: "
                                  (interactive-output-port))
                         (read (interactive-input-port))
                         (if (and (number? n)
                                  (exact? n)
                                  (integer? n)
                                  (<= 0 n)
                                  (< n (length restarts)))
                             n
                             (begin (newline (interactive-output-port))
                                    (read-n))))))
        (invoke-restart-interactively
         (list-ref restarts (read-n))
         (interactive-input-port)
         (interactive-output-port)))))

; Private.

(define r5rs-exception-handler
  (lambda (exception . ignored)
    (choose-restarts (find-restarts
                      interactive
                      (make-condition exception)))))

(define r5rs-domain-exception-handler
  (lambda (exception retry args . ignored)
    (choose-restarts (find-restarts
                      interactive
                      (make-condition exception
                                      'procedure retry
                                      'args args)))))

; An application program might define an ADD-ONE-EXCEPTION
; as follows.

(define add-one-exception
  (make-exception "add-one" arg1-has-wrong-type-exception))

; This exception could be signalled from ADD-ONE as follows.

(define (add-one x)
  (if (integer? x)
      (+ x 1)
      (signal add-one-exception add-one (list x))))

; An exception handler for this particular exception that
; silently coerces to an integer can be written and used
; as follows.

(define (add-one-exception-handler exception proc args . ignored)
   (if (and (not (null? args))
            (number? (car args)))
       (add-one (round (real-part (car args))))
       (signal arg1-has-wrong-type-exception proc args)))

(call-with-handler
 add-one-exception
 add-one-exception-handler
 (lambda () ...))

; Kelsey's example becomes

(define (add-one x)
  (if (integer? x)
      (+ x 1)
      (call-with-restart
       interactive
       (lambda (condition)
         (exception<? add-one-exception
                      (condition-exception condition)))
       add-one
       (lambda (out)
         (display "Give an int to ADD-ONE" out))
       (lambda (in out)
         (display "Gibt mir ein int, bitte: " out)
         (add-one (read in)))
       (lambda ()
         (signal add-one-exception add-one (list x))))))

; End of example
; and of rearranged proposal.