Next: stdlib exceptions conditions, Up: stdlib exceptions [Index]
This section describes Scheme’s exception–handling and
exception–raising constructs provided by the (rnrs exceptions (6))
library.
Exception handlers are one–argument procedures that determine the action the program takes when an exceptional situation is signalled. The system implicitly maintains a current exception handler.
The program raises an exception by invoking the current exception handler, passing it an object encapsulating information about the exception. Any procedure accepting one argument may serve as an exception handler and any object may be used to represent an exception.
The system maintains the current exception handler as part of the dynamic environment of the program (see scheme basic dynamic extent).
When a program begins its execution, the current exception handler is
expected to handle all &serious
conditions by interrupting
execution, reporting that an exception has been raised, and displaying
information about the condition object that was provided. The handler
may then exit, or may provide a choice of other options. Moreover, the
exception handler is expected to return when passed any other
non–&serious
condition. Interpretation of these expectations
necessarily depends upon the nature of the system in which programs are
executed, but the intent is that users perceive the raising of an
exception as a controlled escape from the situation that raised the
exception, not as a crash.
handler must be a procedure and should accept one argument.
thunk must be a procedure that accepts zero arguments. The
with-exception-handler
procedure returns the results of invoking
thunk. handler is installed as the current exception
handler for the dynamic extent (as determined by dynamic-wind
) of
the invocation of thunk.
Implementation responsibilities: The implementation must check
the restrictions on handler to the extent performed by applying it
as described when it is called as a result of a call to raise
or
raise-continuable
. An implementation may check whether
handler is an appropriate argument before applying it.
Each ?cond-clause is as in the specification of cond
,
baselib expressions derived cond. ‘=>’ and ‘else’ are
the same as in the (rnrs base (6))
library.
Evaluating a guard
form evaluates ?body with an exception
handler that binds the raised object to ?variable and within the
scope of that binding evaluates the clauses as if they were the clauses
of a cond
expression. That implicit cond
expression is
evaluated with the continuation and dynamic environment of the
guard
form. If every ?cond-clause’s ?test evaluates
to #f
and there is no else
clause, then
raise-continuable
is re–invoked on the raised object within the
dynamic environment of the original call to raise
except that the
current exception handler is that of the guard
expression.
The final expression in a cond
clause is in a tail context if the
guard
expression itself is.
Raise a non–continuable exception by invoking the current exception
handler on obj. The handler is called with a continuation whose
dynamic environment is that of the call to raise
, except that the
current exception handler is the one that was in place when the handler
being called was installed. When the handler returns, a
non–continuable exception with condition type &non-continuable
is raised in the same dynamic environment as the handler.
Raise a continuable exception by invoking the current exception
handler on obj. The handler is called with a continuation that is
equivalent to the continuation of the call to raise-continuable
,
with these two exceptions:
If the handler returns, the values it returns become the values returned
by the call to raise-continuable
.
Miscellaneous examples:
(guard (con ((error? con) (if (message-condition? con) (display (condition-message con)) (display "an error has occurred")) 'error) ((violation? con) (if (message-condition? con) (display (condition-message con)) (display "the program has a bug")) 'violation)) (raise (condition (make-error) (make-message-condition "I am an error")))) -| I am an error ⇒ error (guard (con ((error? con) (if (message-condition? con) (display (condition-message con)) (display "an error has occurred")) 'error)) (raise (condition (make-violation) (make-message-condition "I am an error")))) ⇒ exception &violation (guard (con ((error? con) (display "error opening file") #f)) (call-with-input-file "foo.scm" read)) -| error opening file ⇒ #f (with-exception-handler (lambda (con) (cond ((not (warning? con)) (raise con)) ((message-condition? con) (display (condition-message con))) (else (display "a warning has been issued"))) 42) (lambda () (+ (raise-continuable (condition (make-warning) (make-message-condition "should be a number"))) 23))) -| should be a number ⇒ 65
Given a single–thread program, the following implementation of
with-exception-handler
, raise
and raise-continuable
is compliant:
(import (except (rnrs) with-exception-handler raise raise-continuable guard)) (define default-error-port (current-error-port)) (define stack-of-handlers (list (lambda (raised-object) (display "unhandled exception:\n" default-error-port) (display raised-object default-error-port) (newline default-error-port) (when (serious-condition? raised-object) (exit -1))) (lambda args (exit -1)))) (define-syntax %with-popped-handler (syntax-rules () ((_ ?handler . ?body) (let ((ell stack-of-handlers) (?handler (car stack-of-handlers))) (dynamic-wind (lambda () (set! stack-of-handlers (cdr ell))) (lambda () . ?body) (lambda () (set! stack-of-handlers ell))))))) (define (with-exception-handler handler thunk) (let ((ell stack-of-handlers)) (dynamic-wind (lambda () (set! stack-of-handlers (cons handler ell))) thunk (lambda () (set! stack-of-handlers ell))))) (define (raise-continuable raised-object) (%with-popped-handler handler (handler raised-object))) (define (raise raised-object) (%with-popped-handler handler (handler raised-object) (raise (condition (make-non-continuable-violation) (make-message-condition "return from handler of non-continuable exception")))))
we see that no function rewinds the stack nor invokes a continuation;
also, in raise
, the recursive invocation raising a
non–continuable violation can return to the caller if the handler
returns.
The implementation of the guard
syntax needs some reasoning to be
understood; first, the skeleton of the transformation is a
with-exception-handler
syntax, the following form:
(guard (E ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta)) ((condition-gamma-value E) => (lambda (value) (do-gamma value))) (else (do-else))) (form-a) (form-b))
is basically transformed to:
(with-exception-handler (lambda (E) (cond ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta)) ((condition-gamma-value E) => (lambda (value) (do-gamma value))) (else (do-else)))) (lambda () (form-a) (form-b)))
to make it compliant, we need to add to this transformation the correct handling of continuations and of the dynamic environment.
We need the mechanism explained step by step in what follows; given the form:
(alpha ... (beta ...))
in which the form beta
is nested at some level in the form
alpha
, we want to answer the question: how do we evaluate the
form beta
so that it has the same continuation and dynamic
environment of alpha
?
We begin by understanding that this chunk of code:
(begin (form-a) (form-b))
can be rewritten in the following equivalent form, in which an anonymous function is created and just evaluated in place:
((lambda () (begin (form-a) (form-b))))
or more explicitly:
(apply (lambda () (begin (form-a) (form-b))))
we should understand that, from the point of view of the executed
computation, the apply
and begin
forms have the same
continuation.
The last version can be rewritten as:
(apply (call/cc (lambda (kont) (lambda () ;body (begin (form-a) (form-b))))))
in which the call/cc
form evaluates to the anonymous function
commented as ‘body’ which is then evaluated by apply
; so we
can say again that, from the point of view of the executed computation,
the apply
and begin
forms have the same continuation. We
notice that the escape procedure kont
is not used.
Let’s consider the following transformation of the previous example and understand why it is equivalent:
(apply (call/cc (lambda (kont) (lambda () ;shell (kont (lambda () ;body (begin (form-a) (form-b))))))))
the call/cc
form evaluates to the anonymous function commented as
‘shell’ which is evaluated by apply
; the kont
escape
procedure jumps back to the continuation of call/cc
which is
again apply
and its return value is the anonymous function
commented as ‘body’ which is evaluated by apply
. So once
again we can say that, from the point of view of the executed
computation, the apply
and begin
forms have the same
continuation.
As a final consideration, let’s modify the form as follows:
(apply (call/cc (lambda (kont) (lambda () ;shell (dynamic-wind (lambda () (in-guard)) (lambda () (kont (lambda () ;body (begin (form-a) (form-b))))) (lambda () (out-guard)))))))
when the shell
function is evaluated, the dynamic environment may
be modified by the dynamic-wind
form’s in-guard
and
out-guard
, but the begin
form is still evaluated with the
same continuation and the same dynamic environment of apply
: both
in-guard
and out-guard
are evaluated before the
body
function is evaluated.
Now we can go back to the guard
implementation and consider a
guard
form with else
clause:
(guard (E ((alpha-condition? E) (do-alpha)) (else (do-else))) (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard))))
we observed that the skeleton expansion of guard
is:
(with-exception-handler (lambda (E) (cond ((alpha-condition? E) (do-alpha)) (else (do-else)))) (lambda () (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard)))))
the report establishes that:
That implicit
cond
expression is evaluated with the continuation and dynamic environment of theguard
form.
using the mechanism described above, we can expand the guard
form
as follows:
((call/cc (lambda (guard-kont) (lambda () (with-exception-handler (lambda (E) ;shell (guard-kont (lambda () ;handler (cond ((alpha-condition? E) (do-alpha)) (else (do-else)))))) (lambda () ;body (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard)))))))))
if the anonymous function commented as body
raises an exception,
the handler function commented as shell
is applied to the raised
object; the guard-kont
escape procedure is used to jump to the
continuation of call/cc
and then the cond
form is
evaluated. This expansion is compliant. We notice explicitly that:
raise
or other form which raised the exception,
rather it returns to the continuation of guard
.
guard-kont
is invoked: out-guard
is evaluated and
only after that the anonymous function commented as handler
is
evaluated. This is different from the default behaviour of
with-exception-handler
, in which the handler is evaluated in the
same dynamic environment of the body function with the single difference
of the current exception handler.
Now let’s consider a guard
form without else
clause:
(guard (E ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta))) (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard))))
applying the same expansion described above yields:
(apply (call/cc (lambda (guard-kont) (lambda () (with-exception-handler (lambda (E) ;shell (guard-kont (lambda () ;handler (cond ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta)))))) (lambda () ;body (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard)))))))))
but the report establishes that:
If every ?cond-clause’s ?test evaluates to
#f
and there is noelse
clause, thenraise-continuable
is re–invoked on the raised object within the dynamic environment of the original call toraise
except that the current exception handler is that of theguard
expression.
so we use again the same mechanism and expand as follows:
(apply ;external apply (call/cc (lambda (guard-kont) (lambda () (with-exception-handler (lambda (E) ;shell (apply ;internal apply (call/cc (lambda (reraise-kont) (guard-kont (lambda () ;handler (cond ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta)) (else (reraise-kont (lambda () ;reraise (raise-continuable E)))))) ))))) (lambda () ;body (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard)))))))))
if the body function raises an exception and one of the non–else
clauses matches, everything works as above and the cond
form is
evaluated with the continuation of guard
; but if the clause which
matches is else
: the reraise-kont
escape procedure is used
to jump back to the continuation of the internal call/cc
which
has a dynamic environment equal to the one of the original raise
invocation; the return value of reraise-kont
is the anonymous
function commented as reraise
, which is evaluated in place by the
internal apply
; the return value of raise-continuable
becomes the argument of guard-kont
.
We notice explicitly that:
in-guard
is
evaluated; if form-a
raises an exception: guard-kont
is
evaluated and so out-guard
is evaluated.
else
clause is evaluated, then reraise-kont
is
evaluated: in-guard
is evaluated again before
raise-continuable
; if raise-continuable
returns:
guard-kont
is evaluated and out-guard
is evaluated again.
As the last touch, let’s introduce a binding for the raised object as follows:
(apply ;external apply (call/cc (lambda (guard-kont) (lambda () (with-exception-handler (lambda (D) ;shell (apply ;internal apply (call/cc (lambda (reraise-kont) (guard-kont (lambda () ;handler (let ((E D)) (cond ((alpha-condition? E) (do-alpha)) ((beta-condition? E) (do-beta)) (else (reraise-kont (lambda () ;reraise (raise-continuable D))))))) ))))) (lambda () ;body (dynamic-wind (lambda () (in-guard)) (lambda () (form-a)) (lambda () (out-guard)))))))))
this way we are sure to reraise the original raised object in the
else
clause even though the tests in the clauses may have
modified the biding of E
or its value.
Next: stdlib exceptions conditions, Up: stdlib exceptions [Index]