Next: Throw, Previous: Throw Handlers, Up: Exceptions
Before version 1.8, Guile's closest equivalent to
with-throw-handler
was lazy-catch
. From version 1.8
onwards we recommend using with-throw-handler
because its
behaviour is more useful than that of lazy-catch
, but
lazy-catch
is still supported as well.
A lazy catch is used in the same way as a normal catch
,
with key, thunk and handler arguments specifying the
exception type, normal case code and handler procedure, but differs in
one important respect: the handler procedure is executed without
unwinding the call stack from the context of the throw
expression
that caused the handler to be invoked.
This behaves exactly like
catch
, except that it does not unwind the stack before invoking handler. If the handler procedure returns normally, Guile rethrows the same exception again to the next innermost catch, lazy-catch or throw handler. If the handler exits non-locally, that exit determines the continuation.
The above
scm_lazy_catch
takes Scheme procedures as body and handler arguments.scm_internal_lazy_catch
is an equivalent taking C functions. Seescm_internal_catch
(see Catch) for a description of the parameters, the behaviour however of course followslazy-catch
.
Typically handler is used to display a backtrace of the stack at
the point where the corresponding throw
occurred, or to save off
this information for possible display later.
Not unwinding the stack means that throwing an exception that is caught
by a lazy-catch
is almost equivalent to calling the
lazy-catch
's handler inline instead of each throw
, and
then omitting the surrounding lazy-catch
. In other words,
(lazy-catch 'key (lambda () ... (throw 'key args ...) ...) handler)
is almost equivalent to
((lambda () ... (handler 'key args ...) ...))
But why only almost? The difference is that with
lazy-catch
(as with normal catch
), the dynamic context is
unwound back to just outside the lazy-catch
expression before
invoking the handler. (For an introduction to what is meant by dynamic
context, See Dynamic Wind.)
Then, when the handler itself throws an exception, that exception
must be caught by some kind of catch
(including perhaps another
lazy-catch
) higher up the call stack.
The dynamic context also includes with-fluids
blocks
(see Fluids and Dynamic States),
so the effect of unwinding the dynamic context can also be seen in fluid
variable values. This is illustrated by the following code, in which
the normal case thunk uses with-fluids
to temporarily change the
value of a fluid:
(define f (make-fluid)) (fluid-set! f "top level value") (define (handler . args) (cons (fluid-ref f) args)) (lazy-catch 'foo (lambda () (with-fluids ((f "local value")) (throw 'foo))) handler) => ("top level value" foo) ((lambda () (with-fluids ((f "local value")) (handler 'foo)))) => ("local value" foo)
In the lazy-catch
version, the unwinding of dynamic context
restores f
to its value outside the with-fluids
block
before the handler is invoked, so the handler's (fluid-ref f)
returns the external value.
lazy-catch
is useful because it permits the implementation of
debuggers and other reflective programming tools that need to access the
state of the call stack at the exact point where an exception or an
error is thrown. For an example of this, see REFFIXME:stack-catch.
It should be obvious from the above that lazy-catch
is very
similar to with-throw-handler
. In fact Guile implements
lazy-catch
in exactly the same way as with-throw-handler
,
except with a flag set to say “where there are slight differences
between what with-throw-handler
and lazy-catch
would do,
do what lazy-catch
has always done”. There are two such
differences:
with-throw-handler
handlers execute in the full dynamic context
of the originating throw
call. lazy-catch
handlers
execute in the dynamic context of the lazy-catch
expression,
excepting only that the stack has not yet been unwound from the point of
the throw
call.
with-throw-handler
handler throws to a key that does not
match the with-throw-handler
expression's key, the new
throw may be handled by a catch
or throw handler that is _closer_
to the throw than the first with-throw-handler
. If a
lazy-catch
handler throws, it will always be handled by a
catch
or throw handler that is higher up the dynamic context than
the first lazy-catch
.
Here is an example to illustrate the second difference:
(catch 'a (lambda () (with-throw-handler 'b (lambda () (catch 'a (lambda () (throw 'b)) inner-handler)) (lambda (key . args) (throw 'a)))) outer-handler)
This code will call inner-handler
and then continue with the
continuation of the inner catch
. If the
with-throw-handler
was changed to lazy-catch
, however, the
code would call outer-handler
and then continue with the
continuation of the outer catch
.
Modulo these two differences, any statements in the previous and following subsections about throw handlers apply to lazy catches as well.