The clause for `set!`

in our CPS interpreter looks like

(Set! (name exp) (eval exp env (lambda (v) (set-box! (lookup env name) v) (k #f))))This is meta-circular, so it is not a very good explanation of assignment, unless you already know what assignment is.

Let `env : name -> loc`

and `store : loc -> value`

where `store`

is a new parameter to the interpreter.
The store is an explicit map representing the contents of the boxes
in the earlier interpreter.
Adding the store parameter,
our interpreter looks like this:

(define eval (lambda (e env s k) (variant-case e (Const (value) (k value)) (Var (name) (k (lookup s (lookup env name)))) (Lam (formal body) (k (lambda (varg s2 k2) (let ((new-cell (length s2))) (eval body (extend env formal new-cell) (extend s2 new-cell varg) k2))) s)) (Ap (fun arg) (eval fun env s (lambda (vfun s1) (eval arg env s1 (lambda (varg s2) (vfun varg s2 k)))))) (Set! (name exp) (eval exp env s (lambda (vexp s1) (k #f (extend s1 (lookup env name) vexp))))) (Letcc (name body) (let ((new-cell (length s))) (eval body (extend env name new-cell) (extend s new-cell (lambda (v s2 k2) (k v s2))) k))))))Notice that the store is passed explicitly from each clause to the continuation. This is how we mimic assignment in languages like Haskell that are purely functional.

The store is single threaded: only one store is ever in use at one
time. To see this, observe that `eval`

and all continuation
lambdas are always called in tail position. Look at each call, and
verify that whenever a new store is constructed, the old one is not
used again. Hence
we can pull out the store argument and make it a global variable.

(define s ...) ... (Set! (name exp) (eval exp env s (lambda (v) (set! s (extend s (lookup env name) v)) (k #f)))) (Lam (formal body) (k (lambda (arg k2) (let ((new-cell (length s))) (set! s (extend s new-cell arg)) (eval body (extend env formal new-cell) k2)))))But this interpreter does not capture all allocation explicitly--the continuation lambdas allocate. To fix this, let's use the first-order transformation to make continuations into records.

(define eval (lambda (e env s k) (variant-case e (Const (value) (apply-k k value)) (Var (name) (apply-k k (lookup s (lookup env name)))) (Lam (formal body) (apply-k k (make-Closure body env formal))) (Ap (fun arg) (eval fun env (make-After-fun arg env k)))))) (define apply-k (lambda (k v) (variant-case k (After-fun (arg env next) (eval arg env (make-After-arg v next))) (After-arg (vfun next) (variant-case vfun (Closure (body env formal) (let (new-cell (length s)) (set! s (extend s new-cell v)) (eval body (extend env formal new-cell) next))))))))Now we can see the remaining allocation:

`make-After-fun, make-Closure, make-After-arg`

.
Let's put them in the store.
(define alloc (lambda () (length s))) (define store-set! (lambda (loc val) (set! s (extend s loc val)))) (define store-ref (lambda (loc) (lookup s loc))) ... (Ap (fun arg) (let ((new-cell (alloc))) (store-set! new-cell (make-After-fun arg env k)) (eval fun env new-cell)))This still doesn't quite look like the allocations are going in the store. Let's try again. Rather than use make-After-fun, we will explicitly allocate 4 cells to hold the three fields of an After-fun plus a ``tag''.

(define p 0) (define alloc (lambda (n) (let ((cell p)) (set! p (+ p n)) cell))) ... (Ap (fun arg) (let ((cell (alloc 4))) (store-set! cell 'After-fun) (store-set! (+ 1 cell) arg) (store-set! (+ 2 cell) env) (store-set! (+ 3 cell) k) (eval fun env cell)))

`extend`

calls also allocate.
Making these allocations explicit is similar to making
continuation allocations explicit.
Now we have a CPS interpreter in which all allocation is explicit. All
`eval`

calls are tail calls, which are essentially jumps with
arguments. But what is a jump with arguments?

`Pi`

.
Since it never uses its arguments after making a call
(as it is tail-recursive), we can modify the arguments as follows:
(define Pi (lambda (l acc) (if (null? l) acc (let ((arg1 (cdr l)) (arg2 (* (car l) acc))) (set! l arg1) (set! acc arg2) (Pi l acc)))))Notice that

`Pi`

passes its own arguments to itself.
The arguments are in different boxes (check the evaluator - a new box is
constructed for each argument at each call). But the old boxes are never
needed after the next call to `Pi`

because the function is
tail-recursive and never returns. So we could use the same boxes.
We do this by using global variables.
(define l #f) (define acc #f) (define Pi (lambda () (if (null? l) acc (let ((arg1 (cdr l)) (arg2 (* (car l) acc))) (set! l arg1) (set! acc arg2) (Pi)))))

`Pi`

no longer takes an arguments. We can think of top level
arguments as registers. Let's do the same thing to our evaluator.
(define e #f) (define env #f) (define k #f) (define eval (lambda () (variant-case e (Ap (fun arg) (let ((new-cell (alloc))) (set-store! new-cell (make-After-fun arg env k)) (set! e fun) (set! k new-cell) (eval))))))This final interpreter

- is in CPS
- has only lambdas with no arguments
- treats all calls as jumps (gotos)
- has all allocation explicit.

- Finish the first-order transform on the evaluator that makes allocation explicit.
- Finish registerizing the last evaluator.

- EOPL Chapter 10.