Intro to continuations and CPS
Questions
Exam Grades
- Grades calculations
- Most grades in
Homework
- Excited to get back into it
- First part of your final project coming soon!
Continuation-passing Style
Given (f (g (h i) j) k)
what can be done first? => (h i)
Why? It must be evaluated before (g(h i)j).
How about (f (g (h i) (j l)))
? => either (h i)
or (j l)
.
How to take control?
Start with the following expression:
(hi
(lambda(hi)
...))
- Assume that hi is the result of applying
(h i)
- drop in everything else to replace the
...
so … (f (g (h i) (j l)))
becomes
(hi
(lamda (hi)
(f (g hi (j l)))
(lambda (hi) (f (g hi (j l))))
is a continuation because:
hi
appears in the body of the continuation oncehi
is intented to replace(h i)
and only(h i)
Write rember*
in CPS
In direct style
(define rember8
(lambda(ls)
(cond
[(empty? ls) '()]
[(eqv? (car ls) 8) (cdr ls)]
[else (cons (car ls) (rember8 (cdr ls)))])))
- Serious:
How to CPS expressions:
To convert to CPS style use the following rules
Rule 1: when ever we see a lambda in the code and we want to use CPS style then A. add an argument B. process the body
so (lambda (x ...) ...) => (lambda (x ... k)...^)
Start by added the argument
(define rember8
(lambda (ls k)
(cond
[(null? ls) '()]
[(eqv? (car ls) 8) (cdr ls)]
[else (cons (car ls) (rember8 (cdr ls)))])))
Rule 2: Don’t sweat the small stuff!!!! -small stuff is stuff we know will terminate right away -don’t sweat it if you know it will be evaluated -if it might be evaluated, instead pass it to k
Ex. (empty? ls)
-we know it will be evaluated
-we know it is small stuff
-we don’t worry about it
So what do you do with the '()
that is returned as the answer to (empty? ls)
?
=> pass it to k
(define rember8
(lambda(ls k)
(cond
[(empty? ls) (k '()) ] ;;small stuff
[(eqv? (car ls) 8) (k (cdr ls))] ;;small stuff
[else (cons (car ls) (rember8 (cdr ls)))] ;; not small stuff
)))
[else (cons (car ls) (rember8 (cdr ls)))]
is not small stuff so
need to build a new continuation
there is still small stuff in the body so we pass it to k
the else
line is shown on the following
Tail position
Non-tail position
Caution about tail position
;rember8 that is CPSed
(define rember8
(lambda (ls k)
(cond
[(empty? ls) (k '()) ]
[(eqv? (car ls) 8) (k (cdr ls))]
[else (rember8 (cdr ls)
(lambda (x)
(k
(cons (car ls) x))))]
)))
how do you invoke it? => we need a k
and ls
to pass in as follows
(rember8 '() k) => '()
k
could be the identify function (lambda (x) x)
(rember8 '(1 2 8 3 4 6 7 8 5) (lambda (x) x))
What properties can be observed about this program?
- All non-small stuff calls are tail calls
EX surround tail calls with
*
here.
(define rember8
(lambda (ls *k*)
(cond
[(empty? ls) (*k* '()) ]
[(eqv?(car ls) 8) (*k* (cdr ls))]
[else (*rember8* (cdr ls)
(lambda (x)
(*k*
(cons (car ls) x))))]
)))
Simple vs Serious
Why don’t empty?
, eqv?
, car
, cdr
, and cons
count?
- they are small stuff (if we combine small stuff together in small things the combination remains small)
- All arguments = small stuff lambda = small stuff
This is essentially a ‘C’ program. Just convert the continuation to data structures. (We did this with closures)
Simple expressions
Serious expressions
- We decide
How about we trace (rember8 '(1 2 8 3 4 6 7 8 5) (lambda (x) x))
(ls | k
)
'(1 2 8 3 4 6 7 8 5) | (lambda (x) x) = id
'(2 8 3 4 6 7 8 5) | (lambda (x)
(id (cons 1 x))) = k2
'(8 3 4 6 7 8 5) | (lambda (x)
(k2 (cons 2 x))) = k3
...
Once we hit 8 we apply (k (cdr ls))
k = k3
ls = '(8 3 4 6 7 8 5)
'(8 3 4 6 7 8 5) | (lambda (x)
(k2 (cons 2 x))) = k3
(k3 '(3 4 6 7 8 5)) =>
(k2 (cons 2 '(3 4 6 7 8 5))) =>
(id (cons 1 '(2 3 4 6 7 8 5))) =>
(id '(1 2 3 4 6 7 8 5)) => '(1 2 3 4 6 7 8 5)
Done
Another example
;; remove all 8's
(define multirember8
(lambda (ls)
(cond
[(empty? ls) '()]
[(eqv? (car ls) 8) (multirember8 (cdr ls))]
[else (cons (car ls) (multirember8 (cdr ls)))])))
Let’s CPS this thing
(define multirember8
(lambda (ls k)
(cond
[(empty? ls) (k '())]
[(eqv? (car ls) 8) (multirember8 (cdr ls))] ;; **awe snap
[else (multirember8 (cdr ls)
(lambda (x)
(k (cons (car ls) x))))])))
multirember8 takes two arguments => so you need to make another continuation
(define multirember8
(lambda (ls k)
(cond
[(empty? ls) (k '())]
[(eqv? (car ls) 8) (multirember8 (cdr ls)
(lambda (x)
(k x)))]
[else (multirember8 (cdr ls)
(lambda (x)
(k (cons (car ls) x))))])))
what does (lambda (x) (k x)) do?
=> it takes the whatever is passed “x” and passes it to k
Sooo …
Eta reduction: (lambda (x) (M x)) should go to M if x is not free in M & M is going to terminate
M is any arbitrary expression that satisfies the above rule (does not have to be a single variable like k)
so when every you see a tail call you dont even need to think about Eta => just pass k to it
(define multirember8
(lambda (ls k)
(cond
[(empty? ls) (k '())]
[(eqv? (car ls) 8) (multirember8 (cdr ls)
k)]
[else (multirember8 (cdr ls)
(lambda (x)
(k (cons (car ls) x))))])))
Special/tricky cases
let
cond
Example
(define !
(lambda (n)
(if (zero? n)
1
(* n (! (sub1 n))))))
You try
(define extend-env
(lambda (x a env)
(lambda (y)
(if (eqv? x y)
a
(apply-env env y)))))
(define apply-env
(lambda (env y)
(env y)))
Acks
Much of the preceding was borrowed from notes by Adam Foltzer.