1
votes

im currently writing a compiler in OCaml for a subset of scheme and am having trouble understanding how to compile with continuations. I found some great resources, namely:

Using the anormal transformation introduced in the anormal-paper, I now have code where function calls are either bound to a variable or returned.

Example:

(define (fib n)
  (if (<= n 1)
      n
      (+ (fib (- n 1)) 
         (fib (- n 2)))))

becomes:

(define (fib n)
  (let ([c (<= n 1)])
    (if c
        n
        (let ([n-1 (- n 1)])
          (let ([v0 (fib n-1)])
             (let ([n-2 (- n 2)])
               (let ([v1 (fib n-2)])
                 (+ v0 v1)))))))

In order to cps-transform, I now have to:

  1. add cont-parameters to all non-primitive functions
  2. call the cont-parameter on tail-positions
  3. transform all non-primitive function calls, so that they escape the let-binding and become an extra lambda with the previous let-bound variable as sole argument and the previous let-body as the body

The result would look like:

(define (fib n k)
  (let ([c (<= n 1)])
    (if c
        (k n)
        (let ([n-1 (- n 1)])
          (fib n-1 
            (lambda (v0) 
              (let ([n-2 (- n 2)]) 
                (fib n-2
                  (lambda (v1) 
                    (k (+ v0 v1))))))))))

Is this correct?

The csmu-course also talks about how Programs in CPS require no stack and never return. Is that because we don't need to to save the adresses to return to and closures as well as other datatypes are stored on the heap and references are kept alive by using the closures?

The csmu also talks about desugaring of call/cc:

(call/cc) => ((lambda (k f) (f k k)))

when using such desugaring, how does:

(+ 2 (call/cc (lambda (k) (k 2))))

in MIT-Scheme return 4, since the current continuation would probably be something like display?

2

2 Answers

1
votes

is this correct?

(define (fib n k)
  (let ([c (<= n 1)])
    (if c
        (k n)
        (let ([n-1 (- n 1)])
          (fib n-1 
            (lambda (v0) 
              (let ([n-2 (- n 2)]) 
                (fib n-2
                  (lambda (v1) 
                    (k (+ v0 v1))))))))))

you get an A+ 💯


The csmu-course also talks about how Programs in CPS require no stack and never return. Is that because we don't need to to save the addresses to return to and closures as well as other datatypes are stored on the heap and references are kept alive by using the closures?

Exactly! See Chicken Complilation Process for an in-depth read about such a technique.


The csmu also talks about desugaring of call/cc:

(call/cc) => ((lambda (k f) (f k k)))

That doesn't look quite right. Here's a desugar of call/cc from Matt Might -

call/cc => (lambda (f cc) (f (lambda (x k) (cc x)) cc))
0
votes

The essence of the idea of compiling with continuations is that you want to put an order on the evaluation of arguments passed to each function and after you evaluate that argument you send its value to the continuation passed.

It is required for the language in which you rewrite the code in CPS form to be tail recursive, otherwise it will stack empty frames, followed only by a return. If the implementation language does not impose tail-recursion you need to apply more sophisticated methods to get non-growing stack for cps code.

Take care, if you do it, you also need to change the signature of the primitives. The primitives will also be passed a continuation but they return immediately the answer in the passed continuation, they do not create other continuations.

The best reference about understanding how to compile with continuations remains the book of Andrew W. Appel and you need nothing more.