[racket] call-with-composable-continuation vs. call/cc

From: Casey Klein (clklein at eecs.northwestern.edu)
Date: Fri Mar 25 05:37:52 EDT 2011

On Sun, Mar 20, 2011 at 4:58 PM, Gregory Woodhouse <gregwoodhouse at me.com> wrote:
> If I write a trivial loop using call/cc
>
> (call/cc
>  (lambda (k)
>   (define (loop)
>     (let ([x (read)])
>       (if (eq? x '())
>           (k x)
>           (loop))))
>   (loop)))
>
> It does exactly what I expect. If I type anything but () it reads again, but if I type () it prints out '() and quits
>
> Welcome to DrRacket, version 5.1 [3m].
> Language: racket; memory limit: 256 MB.
> hello <-- user input
> '() <-- user input
> () <-- user input
> '() <-- output
>>
>
> but if I change call/cc to call-with-composable-continuatiom I see '() printed twice
>
> Welcome to DrRacket, version 5.1 [3m].
> Language: racket; memory limit: 256 MB.
> hello <-- user input
> () <-- user input
> '() <-- output
> '() <-- output
>>
>
> Why is this?

Expressions that appear at the top-level of a module have some
implicit context wrapped around them. This context becomes part of k
in your examples.

When you write the expression e, what's actually run is something more
like this:

(call-with-continuation-prompt
 (λ ()
   ((λ (x) (begin (displayln x) x))
    e)))

Your uses of `call/cc' and `call-with-composable-continuation' both
capture everything up to the continuation prompt, making k represent
the context

((λ (x) (begin (displayln x) x))
 [])

where [] is its "hole" (i.e., the position that receives returned values).

The difference in the two examples is what happens when you apply k.
In the `call/cc' example, applying k throws out the current
continuation

((λ (x) (begin (displayln x) x))
 [])

replaces it with k (which happens to be exactly the same), and places
'() in the hole, causing it to be printed once.

In the `call-with-composable-continuation' example, applying k extends
the current continuation with k, producing this continuation:

((λ (x) (begin (displayln x) x))
 ((λ (x) (begin (displayln x) x))
  []))

When this extended context receives the value '(), it prints it twice.

If you'd like to see this process in action, you could try
experimenting with the Redex model of Racket's control operators. Be
warned, though, that the notation is a little different than what
Racket actually provides.

To get a feel for how the model works, try this program:

#lang racket

(require redex redex/examples/delim-cont/reduce)

(stepper
 :->
 (term (<> ([loop (λ (i)
                    (if (zero? i)
                        0
                        (begin
                          (print i)
                          (loop (+ i -1)))))])
           []
           (print (loop 5)))))

It pops up a window showing the steps taken in the execution of a
simple program.

Programs in the model have the general shape

(<> ([x v] ...) [] e)

where [x v] is a top-level definition, [] is the numbers printed so
far (initially none), and e is an expression.

Here's a program you can run in the model that's similar to your
`call/cc' program.

(<> ([loop (λ (i k)
             (if (zero? i)
                 (k 0)
                 (begin
                   (print i)
                   (loop (+ i -1) k))))])
    []
    (% 0
       ((λ (x)
          (begin
            (print x)
            x))
        (call/cc (λ (k) (loop 5 k)) 0))
       (λ (x) x)))

The model's `%' form is roughly Racket's
`call-with-continuation-prompt' procedure. For now, you can ignore its
first and third sub-expressions. You can also ignore the second
argument to `call/cc' and the `wcm' business that will appear when you
step through the program's execution.

To see the difference that `call-with-composable-continuation' makes,
change `call/cc' to `call/comp' in the model program.



Posted on the users mailing list.