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

Re: exception systems



Thank you, Richard Kelsey, for posting a serious proposal.
I have several questions.

The first question is whether I understand your proposal
well enough to write a handler for your add-one example.

; This handler reports the exception, prompts for an alternative
; argument, and resumes the computation.

(with-handler
 (lambda (condition)
   (let loop ((restarts (find-restarts interactive condition)))
     (cond ((null? restarts)
            condition)
           ((restart? (car restarts))
            (disclose-restart (car restarts)
                              (exception-output-port))
            (invoke-restart-interactively
             (car restarts)
             (exception-input-port)
             (exception-output-port)))
           (else
            (loop (cdr restarts))))))
 (lambda () (add-one 3/2)))

; This handler silently coerces the argument to an integer
; and resumes the computation.

(with-handler
 (lambda (condition)
   (if (and (wrong-type-argument-condition? condition)
            (eq? add-one
                 (wrong-type-argument-condition-proc condition)))
       (let loop ((restarts (find-restarts interactive condition)))
         (cond ((null? restarts)
                condition)
               ((restart? (car restarts))
                (let ((x (wrong-type-argument-condition-arg condition)))
                  (invoke-restart
                   (car restarts)
                   (round (real-part (if (number? x)
                                         x
                                         0.0))))))
               (else
                (loop (cdr restarts)))))
       condition))
 (lambda () (add-one 3/2)))

Is it guaranteed that every element of the list returned by
FIND-RESTARTS is a restart, so the handler doesn't have to check?
Why does FIND-RESTARTS return a list?  Is the list in any
particular order?  Is there any way at all for the handler to
decide which element of the list to use?

In my experience, the two most common actions that can be
taken by an exception handler are

  1.  To retry an operation with different arguments.
  2.  To return a result to the continuation of an operation,
      bypassing the operation altogether.

Many exception handlers need to be capable of either action.
I don't see how the proposed system allows this choice unless
I resort to an ugly kluge such as

(define (add-one x)
  (if (integer? x)
      (+ x 1)
      (call-with-restart
       <restart-tag>
       <condition-predicate>
       add-one                       ; retry
       (lambda (out) #t)             ; discloser, ha
       (lambda (result ignored)      ; return
         result)
       (lambda ()
         (signal (make-wrong-type-argument-condition
                  add-one
                  x))))))

It appears that a restart is typically selected by indexing
in five distinct dimensions:

  1.  the space of handlers (by daisy-chaining)
  2.  the space of restart-tags
  3.  the space of condition classes (as defined by the
      predicates)
  4.  the space of natural numbers (giving the position
      within a list of restarts)
  5.  the space of condition data (the stuff stored in the
      slots of a condition)

There are several things I don't understand about this
indexing.

I assume WITH-HANDLER places its first argument at the head
of the handler chain for the dynamic extent of the call to
WITH-HANDLER.  Is this correct?

I would guess that CALL-WITH-RESTART associates its first
argument with all but the last argument for the dynamic
extent of the call to CALL-WITH-RESTART.  Is this correct?

The process of fetching a restart strikes me as excessively
complicated, unnecessarily inefficient, and unsafe.  I think
the problems have to do with the binding times of the different
dimensions, and that this can be fixed.  I have a few ideas
in that direction, mainly by way of a mechanism for earlier
binding of dimensions 2 and 3.  Right now I just want to ask
questions and explain a few of my concerns, which may turn
out to be unwarranted.

Suppose for a moment that asynchronous interrupts are reflected
onto the exception mechanism.  (Let me remind you that I have
never said this implementation technique should be mandated.
I _have_ said this is an important use of an exception system,
and that any exception system that does not support it well is
flawed.)  Now suppose someone writes a buggy exception (not
interrupt!) handler and tries it out on some interactive
READ/EVAL/PRINT loop:

>>> (with-handler
     (letrec ((loop (lambda (condition)
                      (loop condition))))
       loop)
     (lambda ()
       (signal (make-random-condition))))

Realizing the bug, this particular someone frantically types
command-period (not everyone uses Unix) to interrupt the
infinite loop.  What happens?

Is the handler installed above still the current handler when
it is called?  If so, then the keyboard exception that is
signalled by the interrupt routine gets handled by the
same buggy exception handler that the exception is trying to
abort.

Presumably the interrupt routine should install its own
set of exception handlers before signalling the keyboard
exception.  WITH-HANDLER should be a fast way to do this,
except the handler it installs is monolithic.  An entirely
separate mechanism would be needed to specify the individual
handlers for the exceptions that are signalled as the result
of asynchronous interrupts.

Essentially the same problem arises with synchronous exceptions
that are signalled during the execution of an exception handler
for an unrelated exception, so this doesn't really have anything
to do with interrupts.

WITH-HANDLER installs a handler that gets to see every condition
that is signalled, unless of course WITH-HANDLER is used to
install another handler in front of it.  If the handler doesn't
want to handle a condition, it can pass the condition along to the
next handler in the active chain of handlers.  It doesn't have to
do this, though.  It could handle all conditions by itself.

In other words, you can't install a new handler without using
a mechanism that effectively allows you to throw out all the
existing handlers and start over from scratch.  It would be
safer to provide a mechanism that allows you to install a
handler that will see only the conditions it is designed to
handle.  This could also be more efficient, as it could
eliminate two, three, or maybe even four dimensions of indexing.

I think this can be done without sacrificing function.  In terms
of my catalog of purposes, Kelsey's proposal seems to have been
designed for purposes #2 and #3.  I really do think we can fix
it to satisfy purposes #4, #5, and #6, or at least #4 and #6,
while improving it even for purposes #2 and #3.

I will probably not read my mail until next Monday, so don't
be annoyed if I don't respond to any new developments before
then.  (Hey, you're probably more annoyed when I do respond
than when I don't. :)

Will