2  Call/cc and how to constrain it

Scheme's control operator, call-with-current-continuation (abbreviated call/cc), allows control to transfer to arbitrary points in the program, not just to dynamically enclosing contexts. It does so by providing the user with a continuation, i.e., a procedural representation of the current control context, or more simply, ``the rest of the program''. Invoking this continuation at any point in the program causes that point's current context to be replaced by the context that the continuation represents. The user sees call/cc as a procedure that takes a single unary procedure f as argument. f is called with the current continuation (hence the operator's name). This continuation is a procedure that takes a single argument, which it inserts into the old program context.1

This ability to substitute the current program context by a previously captured snapshot of a program context is simple and powerful [6710], but too low-level to be used straightaway for user-level abstractions. In addition to the difficulty of encoding user abstractions in terms of call/cc, one must also ensure that the abstractions so defined can function without interference from other uses of call/cc. To solve this problem, Friedman and Haynes [5] illustrate a technique for constraining raw call/cc. They define new call/cc operators that call the original call/cc, but instead of directly calling the call/cc-argument on the continuation, they call it on a continuation object or cob, which is a procedure that performs whatever additional constraining tasks are required before calling the actual continuation.

Let us illustrate the cob technique to solve an easily described problem, that of fluid variables. The form let-fluid temporarily extends the fluid environment, *fluid-env*, for the duration of its dynamic extent.

(define *fluid-env* '())

(define-syntax let-fluid
  (syntax-rules ()
    [(let-fluid ((x e) ...) b ...)
     (let ([old-fluid-env *fluid-env*])
       (set! *fluid-env* 
         (append! (list (cons 'x e) ...) *fluid-env*))
       (let ([result (begin b ...)])
         (set! *fluid-env* old-fluid-env)
         result))]))

Fluid variables are accessed using the form fluid, defined globally using define-fluid, and side-effected using set-fluid!:

(define-syntax fluid
  (syntax-rules ()
    [(fluid x)
     (cond [(assq 'x *fluid-env*) => cdr]
           [else (error 'undefined-fluid 'x)])]))

(define-syntax define-fluid
  (syntax-rules ()
    [(define-fluid x e)
     (set! *fluid-env*
       (cons (cons 'x e) *fluid-env*))]))

(define-syntax set-fluid!
  (syntax-rules ()
    [(set-fluid! x e)
     (cond [(assq 'x *fluid-env*)
            => (lambda (c)
                 (set-cdr! c e))]
           [else (error 'undefined-fluid 'x)])]))

This definition fails in the presence of call/cc, because a call to a continuation does not restore the fluid environment to the value it had at the capture of the continuation. A simple cob-based rewrite of call/cc takes care of this:

(define call/cc-f
  (let ([call/cc-orig call/cc])
    (lambda (proc)
      (call/cc-orig
        (lambda (k)
          (let* ([my-fluid-env *fluid-env*]
                 [cob (lambda (v)
                        (set! *fluid-env* my-fluid-env)
                        (k v))])
            (proc cob)))))))

Note that once we've defined the new fluids-aware call/cc variant, it's a good idea to reuse the call/cc name to refer to the variant. In essence, we retire the original call/cc from further use, as it would interfere with the correct functioning of the new operator. In standard Scheme, one could do this by simply re-set!ing the call/cc name, but this has problems as programs scale. In a Scheme with a module system [4], a more robust method is to define a new module that provides the new control operator under the call/cc name.


1 I will ignore the presence of Scheme's multiple values, as they add no particular illumination to the problem we are addressing.