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
condexpression is evaluated with the continuation and dynamic environment of theguardform.
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
#fand there is noelseclause, thenraise-continuableis re–invoked on the raised object within the dynamic environment of the original call toraiseexcept that the current exception handler is that of theguardexpression.
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]