1  unwind-protect that recognizes escaping continuations

In Pitman's first proposal, the new call/cc (let's call it call/cc-new) takes two arguments. The second argument is a procedure that is applied to the current continuation. Whether this continuation is an escaping or a full continuation depends on whether the first argument is true or false. Thus (call/cc-new #t ...) produces escaping continuations, and (call/cc-new #f ...) produces full continuations.5

unwind-protect interacts with call/cc-new as follows. If the unwind-protect body invokes an escaping continuation provided by an enclosing call/cc-new #t, then the postlude is performed. To accomplish this, the cob generated by call/cc-new #t keeps a list (my-postludes) of all the unwind-protect postludes within its dynamic extent. Since, a call to call/cc-new #t cannot know of the unwind-protects that will be called in its dynamic extent, it is the job of each unwind-protect to update the my-postludes of its enclosing call/cc-new #ts. It is enough that we have a fluid variable *current-escape-prompt*. Each call/cc-new #t sets *current-escape-prompt* to the cob it generates, and remembers the previous *current-escape-prompt* as prev-escape-prompt. When an unwind-protect seeks to update the my-postludes list of its nearest *current-escape-prompt*, the updates are propagated via prev-escape-prompt, so all the concentrically enclosing *current-escape-prompt*s become aware of the unwind-protect.

The unwind-protect doctors the postlude that it so propagates. Any time a postlude is executed, it has to be deleted from the my-postludes list of the enclosing *current-escape-prompt*s.

Updating and deleting the my-postludes of a cob is accomplished by making the cob respond to update and delete messages. These messages are unforgeable objects provided by lexical scoping, and thus cannot be generated by external code. To such external code, the cob looks like a regular continuation, and any value it is given will be thrown to the context captured by the continuation (unless we deem it an error by the semantics of unwind-protect.)

There is one other constraint that must be programmed in. Any full continuation captured within the unwind-protect body can be invoked only if the unwind-protect hasn't already been exited, either via normal exit or by an escaping continuation. To implement this, we define a variable *current-unwind-protect-alive?* that is fluidly set by each unwind-protect. *current-unwind-protect-alive?* is a thunk that returns the aliveness of its unwind-protect. An unwind-protect's postlude will remember to turn off the aliveness, so its *current-unwind-protect-alive?* will return false.

A call/cc-new #f creates a full-continuation cob that remembers its nearest unwind-protect's *current-unwind-protect-alive?*, using the variable my-unwind-protect-alive?. Calling the cob implies entering this unwind-protect. By checking my-unwind-protect-alive?, the cob prevents its continuation from being invoked if the unwind-protect has already been exited.

The following is the entire code for the modified call/cc, and unwind-protect. Note that Will Clinger has written a more concise implementation [1] that effectively uses dynamic-wind and thereby avoids much of the variable bookkeeping in my code.

;uwesc.scm
;An unwind-protect in portable Scheme
;  (assumes fluid-let defined in terms of dynamic-wind)
;Dorai Sitaram
;May 11, 2003

;Don't load this file twice, as it
;redefines call/cc.

(define delq!
  (lambda (x s)
    ;destructively removes x from list s,
    ;returns modified s.
    (let loop ((s s))
      (cond ((null? s) s)
            ((eq? (car s) x) (loop (cdr s)))
            (else (set-cdr! s (loop (cdr s)))
                  s)))))

(define call/cc-new #f)
(define unwind-protect-proc #f)

(let ((call/cc-orig call/cc)
      (update (list 'update))
      (delete (list 'delete))
      (*current-escape-prompt* (lambda (v) (lambda (v2) #f))) 
      (*current-unwind-protect-alive?* (lambda () #t)))

  (set! call/cc-new
    (lambda (once? proc)
      (if once?
          (call/cc-orig
            (lambda (k)
              (let ((prev-escape-prompt *current-escape-prompt*)
                    (my-postludes '())
                    (already-called? #f))
                (fluid-let ((*current-escape-prompt*
                              (lambda (v)
                                (cond ((eq? v update)
                                       (lambda (pl)
                                         (set! my-postludes
                                           (cons pl my-postludes))
                                         ((prev-escape-prompt update) pl)))
                                      ((eq? v delete)
                                       (lambda (pl)
                                         (set! my-postludes
                                           (delq! pl my-postludes))
                                         ((prev-escape-prompt delete) pl)))
                                      (already-called?
                                        (error 'calling-escaping-cont-twice))
                                      (else
                                        (set! already-called? #t)
                                        (for-each 
                                          (lambda (pl) (pl))
                                          my-postludes)
                                        (k v))))))
                  (*current-escape-prompt*
                    (proc *current-escape-prompt*))))))
          (call/cc-orig
            (lambda (k)
              (let ((my-unwind-protect-alive? *current-unwind-protect-alive?*))
                (proc
                  (lambda (v)
                    (if (my-unwind-protect-alive?)
                        (k v)
                        (error 
                          'trying-to-enter-exited-unwind-protect))))))))))

  (set! unwind-protect-proc
    (lambda (body postlude)
      (let ((my-escape-prompt *current-escape-prompt*)
            (alive? #t))
        (fluid-let ((*current-unwind-protect-alive?* (lambda () alive?)))
          (letrec ((pl (lambda ()
                         (set! alive? #f)
                         (postlude)
                         ((my-escape-prompt delete) pl))))
            ((my-escape-prompt update) pl)
            (let ((res (body)))
              (pl)
              res))))))

  )

(define-syntax unwind-protect
  (syntax-rules ()
    ((unwind-protect body postlude)
     (unwind-protect-proc 
       (lambda () body) (lambda () postlude)))))

;OR
;
;(define-macro unwind-protect
;  (lambda (body postlude)
;    `(unwind-protect-proc
;       (lambda () ,body) (lambda () ,postlude))))

(set! call/cc call/cc-new)


5 Pitman's text calls the escaping continuations single-use, counting as a use the implicit use of the continuation (i.e., when the call/cc-new expression exits normally without explicitly calling its continuation). There are some design choices on what effect the use of such a continuation has on the use count of other continuations captured within its dynamic extent, whether they be single- or multi-use. For now, I assume there is no effect. If there were, such could be programmed by having cobs propagate messages to their nearest enclosed cob using fluid-variables.