Search code examples
schemeracketevaluationcontinuationscallcc

Execute the following call/cc expression


I use racket and I got the result 4 for following simple code:

(let/cc done
  ((let/cc esc
     (done (+ 1 (let/cc k
                  (esc k)))))
   3))

and I was going to execute this code step-by-step.

First, I changed the first let/cc into the form of call/cc like below:

(call/cc (λ (done)
           ((let/cc esc
              (done (+ 1 (let/cc k
                           (esc k)))))
            3)))

Of course, this produces 4 also.

Second, since I found the mechanism of call/cc in the internet which says call/cc do following 4 steps:

  1. Captures the current continuation.
  2. Constructs a function C that takes one argument, and applies the current continuation with that argument value.
  3. Passes this function as an argument to expr --- i.e., it invokes (expr C).
  4. Returns the result of evaluating (expr C), unless expr calls C, in which case the value that is passed to C is returned.

Thus, I followed above steps for the first call/cc like:

  1. Current continuation is an identity.
  2. C refers (λ (x) x).
  3. Since expr is (λ (done) ((let/cc esc (done (+ 1 (let/cc k (esc k))))) 3)), (expr C) is:

    ((λ (done)
       ((let/cc esc
          (done (+ 1 (let/cc k
                       (esc k)))))
        3))
     (λ (x) x))
    
  4. To return the result value of above code, I execute above in racket.

But, above code (modified by me) is not executed and produces an error:

> application: not a procedure;
>
> expected a procedure that can be applied to arguments
>
>  given: 4
>
>  arguments...:
>
>   3

Please what I did wrong. I'm confusing the concept of continuation. Thanks.


Solution

  • Continuations are not just closures (functions). They also perform a jump to their defining location in code. You have to perform the CPS transformation in full to try evaluating the resulting expression in Scheme interpreter. That expression will only contain lambdas and no continuations (in the sense of call/cc (1)).

    The expression that you tried mixes them both - it defines done as simple lambda-defined function, but it is still used in the nested context as a continuation.


    (1) (another source of confusion is calling the function arguments in the continuation-passing style "continuations". they are not "true" continuations; they are simple functions "to be called" in this or that eventuality, so colloquially they are also referred to as "continuations" although "contingencies" or even "handlers" could be better.)

    See also another example of call/cc code translation.

    Following that approach, translating your Scheme code into Common Lisp, we get:

    ;; (let/cc done
    ;;   ((let/cc esc
    ;;      (done (+ 1 (let/cc k
    ;;                   (esc k)))))
    ;;    3)) 
    (prog  (retval done arg1 func esc arg2 k arg3 arg4)
        (setq done (lambda (x) (setq retval x) (go DONE)))    ; 3
         (setq arg1 3)                                        ; 5
          (setq esc  (lambda (x) (setq func x) (go ESC)))     ; 8
           (setq arg3 1)                                      ; 10
            (setq k  (lambda (x) (setq arg4 x) (go K)))       ; 12
            (setq arg4 (funcall esc k))                       ; 13
      K                                                       ; 11  continuation K
           (setq arg2 (+ arg3 arg4))                          ; 9
          (setq func (funcall done arg2))                     ; 7
      ESC                                                     ; 6   continuation ESC
         (setq retval (funcall func arg1))                    ; 4
      DONE                                                    ; 2   continuation DONE
        (return retval))                                      ; 1
    

    which indeed returns 4 (the lines of code are numbered in order as they are written, during the translation).