Exception handlers Antti Huima (23 Oct 2000 09:14 UTC)
Re: Exception handlers Marc Feeley (05 Jan 2001 03:48 UTC)

Exception handlers Antti Huima 23 Oct 2000 09:14 UTC

It seems that this SRFI has overlived its draft period, so please throw
this to /dev/null if you wish.

Anyway, I think it is not beneficial that an exception handler can return
to the point of throwing the exception.

Assume that `open-input-file' raises an exception if a file is not found
and let:

(with-exception-handler
  (lambda (exception) (display "Funny thing happened") (newline))
  (lambda ()
    (let ((port (open-input-file "foo")))
      (read port))))

Executing this obviously causes `port' to be bound to an undefined value,
after which `read' will trigger an error that will typically be not an
exception but a full-blown crash (sort of).  I think that the common
intuition w/ exception handlers is that they do not return in the standard
way.  Now, as shown in the document, the exception handler can invoke an
explicitly captured escape continuation.  However, if
with-exception-handler would capture the continuation internally, its
implementation could be possibly optimized.  Moreover, this would inhibit
the problem above appearing.

Whatever exception systems will ever pop out, I would like to use such one
that would define with-exception-handler as:

(with-exception-handler handler thunk)                ;procedure

  Returns the result(s) of (thunk), unless an exception is (raise)d, in
  which case the continuation waiting for the value of (raise) is
  discarced, (handler exception) is called, and the value(s) of this call
  is (are) returned.  The number of values returned by (thunk) and
  by (handler exception) for any exception must match the number of
  values the continuation of the call to (with-exception-handler) expects
  to receive.

Because I like this :-] I thus propose the draft SRFI-18 to be changed
accordingly.

I think it should be also specified that the dynamic extent of `thunk' is
leaved when `handler' starts to execute.  The document was not clear on
this.  Otherwise e.g. (raise) inside `handler' will have undefined
behaviour.

Here an example implementation (assuming single return values), assuming
that the variable `handler' is bound to the current thread's exception
handler that (raise) will invoke.  `$$handler' is, in some magical way, a
thread-local global variable (to avoid assuming fluid-lets or whatever):

(define (with-exception-handler handler thunk)
  (let* ((unique (list #f))
	 (result
	  (call-with-current-continuation
	   (lambda (return)
	     (let ((internal-handler
		    (lambda (exc)
		      (return (cons unique exc))))
		   (old-handler $$handler))
	       (dynamic-wind
		   (lambda () (set! $$handler internal-handler))
		   thunk
		   (lambda () (set! $$handler old-handler))))))))
      (if (and (pair? result) (eq? (car result) unique))
	  (handler (cdr result))
	  result))))

This implementation restores the dynamic environment before handler starts
to execute in the way the Scheme implementatino would restore it
standardly after jumping to a captured continuation.
As opposed to this, the example from the draft:

              (define (g)
                (call-with-current-continuation
                  (lambda (return)
                    (with-exception-handler
                      (lambda (exc)
                        (return
                          (if (string? exc)
                              (string-append "error: " exc)
                              "unknown error")))
                      (lambda ()
                        (write (f 4.))
                        (write (f -1.))
                        (write (f 9.)))))))

runs the handler in the dynamic environment of (thunk) which IMHO cannot
be correct in general.

--
Antti Huima