[racket] design and use of continuation barriers

From: Taylor R Campbell (campbell+racket at mumble.net)
Date: Sat Jul 10 16:45:29 EDT 2010

   Date: Sat, 10 Jul 2010 07:42:56 -0600
   From: Matthew Flatt <mflatt at cs.utah.edu>

   At Fri, 9 Jul 2010 14:56:16 +0000, Taylor R Campbell wrote:
   > First, is there any document generally describing the rationale for
   > the design of continuation barriers and advice about when to use them?

   No, so here's an attempt:

Excellent, thank you for the prompt and detailed reply!

   Use a continuation barrier when calling unknown code and when it's too
   painful to contemplate multiple instantiations of the continuation
   leading to the call.

What should I do if I want to preclude only multiple uses of the
continuation, but still allow single uses even if they are non-local?
For example, consider

(define (walker->streamer walker)
  (lambda (object)
    (lazy
     (reset (walker object
              (lambda (element)
                (shift k (stream-cons element (lazy (k))))))
            stream-nil)))).

(I use SHIFT and RESET here for brevity; of course this should use
explicit prompt tags in practice.)  Now if I write

(define (walk-list list procedure)
  (let loop ()
    (if (pair? list)
        (begin (procedure (car list))
               (set! list (cdr list))
               (loop))))),

it may be too painful for me to contemplate multiple uses of any of
PROCEDURE's continuations, but WALK-LIST behaves perfectly nicely as
an argument to WALKER->STREAMER.  Erecting a continuation barrier
about the call to PROCEDURE precludes this, though.

      Using `dynamic-wind' turns out not to be enough if you have with
      multiple threads:

            (GUI)         (...)    (...)
              |             |        |
         (button push)     (B)      (C)
              |
             (A)

      A `dynamic-wind' post-thunk there could prevent applying the
      continuation `B' at `A', so you won't lose the GUI loop. For
      applying `A' at `B', however, a guard in a `dynamic-wind' pre-thunk
      may be too late; the GUI loop would be duplicated before the
      pre-thunk could complain.

I don't understand this last clause, that the GUI loop would be
duplicated before the pre-thunk could complain.  Are you worried that
throwing into A will trigger DYNAMIC-WIND entrance procedures up in
the `(GUI)' frames that shouldn't be run more than once?  If so, can't
those detect being run more than once and signal an error, as you
suggested below in the setup code?

Is the problem that you want the exception handler enclosing B to
handle the error, rather than any exception handler in the GUI's
dynamic context?  This seems to me to be the only real difference.

   In fact, for many similar cases, a programmer is more likely to use
   `with-handlers' to handle escapes than `dynamic-wind'... and this is
   where we start to want barriers after all. 

That sounds to me like a mistake on the part of the programmer, but
abstractions or idioms appropriate for cleaning up are a separate
discussion, so I'll refrain from getting into that for the moment.

   To help library implementers, a continuation barrier is installed just
   before before an exception handler is called. That way, if it is
   possible for an exception to be raised, the implementer of `L-1' and
   `L-2' can just use `with-handlers' to clean up, instead of having to
   worry about jumps into the library.

Can you be more specific about where continuation barriers are placed?
I just tried jumping about inside and outside handled extents and the
handlers themselves, and didn't hit any continuation barriers:

((call-with-current-continuation
  (lambda (outside)
    (lambda ()
      (with-handlers
          ((continuation?
            (lambda (inside)
              ((call-with-current-continuation
                (lambda (handler)
                  (outside
                   (lambda ()
                     (handler
                      (lambda () (inside (lambda () 0))))))))))))
        (list 1 ((call-with-current-continuation raise))))))))
;Value: (1 0)

It doesn't make a difference whether I pass a second argument to RAISE
-- either #T or #F.  I've tried various different configurations and
nestings of calls to OUTSIDE, INSIDE, and HANDLER, too.

   I've just pushed a change to Racket to use the "rnrs" trick
   internally. Since the trick is implemented within the run-time system,
   it can also hide the continuation-mark difference. In fact, given the
   continuation machinery that we developed a couple of years ago, the
   change was pretty easy; I just hadn't thought about it enough.

So can full continuations now be used to exit across continuation
barriers just like exit-only continuations and prompt aborts, or are
exit-only continuations and prompt aborts now also prohibited from
exiting across continuation barriers?


Posted on the users mailing list.