guile-devel
[Top][All Lists]
Advanced

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

Re: Backtrace and enhanced catch


From: Neil Jerram
Subject: Re: Backtrace and enhanced catch
Date: Wed, 01 Feb 2006 23:04:36 +0000
User-agent: Gnus/5.1007 (Gnus v5.10.7) Emacs/21.4 (gnu/linux)

Kevin Ryde <address@hidden> writes:

>     ;; lazy-catch, but with HANDLER allowed to return
>     (define-public (c-lazy-catch key thunk handler)
>       (catch 'c-lazy-catch
>         (lambda ()
>           (lazy-catch key thunk
>                       (lambda args
>                         (throw 'c-lazy-catch (apply handler args)))))
>         (lambda (key val)
>           val)))

Thanks.  It took me a while to get my head round this, but in the end
it has helped me see how we can put some finishing touches to
catch and lazy-catch so that they are really nice, clear, consistent
and useful.

With these finishing touches, I think you would be able to write
c-lazy-catch as:

(define (c-lazy-catch key thunk handler)
  (catch key
         thunk
         noop
         handler))

The spec for catch's pre-unwind-handler would be that it can exit
either normally or non-locally.  If it exits normally, Guile unwinds
(dynamic context + stack) and then calls the normal (post-unwind)
handler.  If it exits non-locally, that exit determines the
continuation.

The spec for a lazy-catch handler would be nicely consistent with
this.  Again, we say that the lazy-catch handler can exit either
normally or non-locally.  It if exits normally, Guile itself throws
the same key and args again.  If it exits non-locally, that exit
determines the continuation.

For both cases, we need to make sure that a pre-unwind or lazy-catch
handler that rethrows (or, more generally, throws something whose key
matches its own catch/lazy-catch) does not call the same pre-unwind or
lazy-catch handler again recursively.  (For two reasons: first, to
avoid a possible infinite recursion; second, to allow an interesting
new behaviour, chained calling of lazy-catch/pre-unwind handlers up
the dynamic context.)  Guile currently does this by unwinding the
dynamic context before calling the handler, but this has the problem
that the handler does not truly run in the context where the throw
occurred.  Instead of that, we can avoid the recursion quite easily by
adding a "running" field to the lazy_catch/pre_unwind structure, and
using the scm_frame_ API to ensure that this is set when the handler
is running and reset when it is exited (either normally or
non-locally).

Existing lazy-catch handlers usually end with `(apply throw key args)'
to do a rethrow.  A nice consequence of the spec and implementation
just proposed is that such a handler (if it exits by this throw) will
behave exactly the same as if this final expression was not there.

If we implement this under the existing lazy-catch interface, there
would be 3 kinds of incompatible behaviour change.

1. A lazy-catch handler that returns normally would be equivalent to
   ending with a rethrow, whereas currently Guile does a
   scm_misc_error ("throw", "lazy-catch handler did return.",
   SCM_EOL);

2. Lazy-catch handlers would execute in the full dynamic context where
   the throw occurred, including in particular fluid values; whereas
   currently they don't.  (The only bit of dynamic context that the
   current implementation preserves is the stack.)

3. If a lazy-catch handler throws to a key that does not match its own
   lazy-catch, the proposed new implementation could match that key to
   a catch/lazy-catch that is _closer_ to the throw than the first
   lazy-catch; whereas existing Guile would always look for a
   catch/lazy-catch higher up the dynamic context.

   For example:

     (catch 'a
       (lambda ()
         (lazy-catch 'b
           (lambda ()
             (catch 'a
               (lambda ()
                 (throw 'b))
               inner-handler))
           (lambda (key . args)
             (throw 'a))))
       outer-handler)

   Current Guile would handle the (throw 'a) by calling outer-handler,
   and continuing with the continuation of the outer catch.  The
   proposed implementation would call inner-handler and continue with
   the continuation of the inner catch.

   This seems surprising at first, but I reckon it's inevitable if you
   are serious about executing lazy handlers truly lazily - i.e. in
   the full context of the original throw.

I think all these changes are sufficiently obscure that we could get
away with implementing them under lazy-catch.  But if we want to be
ultra-cautious we could keep lazy-catch as it is and introduce
`with-pre-unwind-handler' (or something) with the proposed semantics.

Anyone still reading?  What do you think?

       Neil





reply via email to

[Prev in Thread] Current Thread [Next in Thread]