0 likes | 17 Views
Exploring the concepts of control flow and continuations in Scheme programming, discussing the flow of control mechanisms, current expressions to be evaluated, and what remains to be done with the values. Delving into continuation-passing style (CPS), explicit continuations, tail-recursive form, and examples to grasp these fundamental ideas in Scheme.
E N D
CSSE 304 Day 21 Student questions Midterm Students A B+ B C+ C D+ D F Puzzle Continuations 34 8 6 3 0 3 2 2 There are two more students who decided to drop the course but have not gotten around to doing it yet. They are not included in the table. CPS
Puzzle (3 minutes in a breakout room) (define mystery (lambda ′x ′x)) • Why doesn’t executing this code cause an error? • Can you find an argument (or arguments) such that applying mystery to it/them will run and give an answer, not an error? • Feel free to use a Scheme interpreter to help you discover the answers. • Hint: the reason for the answers combines two familiar Scheme ideas.
For many students, this section is a significant step up in difficulty from anything that we have done previously in this course. CONTINUATIONS AND CPS
Control Flow • Lately we have talked about data-types, scope, binding, and environments • Another important issue is flow of control – What are some of the control flow mechanisms in Java? • In Scheme (or any expression-oriented language), the most basic control-flow issues are – What is the current expression to be evaluated? – Once that is done, what remains to be done with the value of the current expression?
Control flow in Scheme • The two most basic things that affect flow of control in a program are: • The current ______________ to be evaluated. • The __________________ which tells what is to be done with that value in order to complete the entire computation. expression continuation
Scheme Control Flow Details • What is the current expression to be evaluated? • Once that is done, what remains to be done with the value of the current expression to complete the entire computation? • Consider the evaluation of (+ a 5) in the process of evaluating (- 4 (* b (+ a 5))). • What remains to be done with the value of (+ a 5) ? • Can we express that as a procedure? • We can call that procedure the continuation of the (+ a 5) computation • The process of Scheme evaluation can be expressed as –Loop: • Evaluate the current expression • Apply the current continuation to the result • In A18, you will rewrite your interpreter in this style, which is known as continuation-passing style (CPS).
More Examples • What is the continuation of (< x 5) in (if (< x 5) (+ x 3) (* x 2)) ? • What is the continuation of (+ x 3) in (if (< x 5) (+ x 3) (* x 2)) ?
More Examples • What is the continuation of (< x 5) in (if (< x 5) (+ x 3) (* x 2)) ? –(lambda (v) (if v (+ x 3) (* x 2)) • What is the continuation of (+ x 3) in (if (< x 5) (+ x 3) (* x 2)) ? –(lambda (v) v)
A more practical example (define fact (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) • In the evaluation of (fact 5), what is the continuation of the call to (fact 2)? • We see here that continuation is not merely a syntactic notion.
Explicit Continuations • In "normal language" interpreters, continuations are represented by stack frames. • But we may (for various reasons) want to do "stackless" programming. • We pass an explicit continuation to each procedure call, in order to keep the code in tail-form. • Thus it is continuation-passing style (CPS)
Primitive vs. Substantial Procedures • When CPSing our code, we divide the set of procedures into two groups: – Primitive procedures can be called without a continuation argument. This is a superset of what we call “primitive” procedures in our interpreter discussions. – Substantial procedures (I made up this name) expect a continuation argument. • By default, built-in procedures and non-recursive procedures will be considered primitive; recursive procedures are substantial. • Sometimes it will be useful to write a substantial version of a procedure that would normally be primitive.
Tail-recursive form • If all substantial calls (i.e., calls to substantial procedures) are in tail position, then the stack doesn’t have to grow. • Can we write the code for EVERY computation in tail- recursive form? • We will try! • First, what parts of expressions are in tail position?
Tail-position examples • In a tail-form expression – all calls to substantial procedures are in tail position. – I.e., any such call is the last thing to be done in the current procedure application. • Which expressions are in tail position in the following code segments? – (begin e1 e2 e3) – (if e1 e2 e3) – (cond [e1 e2] [e3 e4] … [else e]) – (let ([v1 e1] [v2 e2] …) e) – (e1 e2 e3) ; procedure application.
Tail-position examples • In a tail-form expression – all calls to substantial procedures are in tail position – I.e., each such call is the last thing to be done in the current procedure application. • Which expressions are in tail position in the following code segments? – (begin e1 e2 e3) – (if e1 e2 e3) – (cond [e1 e2] [e3 e4] … [else e]) – (let ([v1 e1] [v2 e2] …) e) – (e1 e2 e3) ; none of the parts are in tail position • In CPS code, only the red expressions may be applications of substantial procedures.
A special case • in (lambda (x) e0… en), the expression enis in tail position. – enis not evaluated when the lambda expression is evaluated. – It only gets evaluated when the procedure is applied. – Then enis the last thing to be evaluated.
Continuation ADT • As close to abstract as we can come • Procedures: – make-k constructs a continuation representation – apply-k Applies a continuation representation to an argument • We will look at and implement two different representations of continuations.
Recall: our first two Environment representations (define extend-env (lambda (syms vals env) (lambda (sym) (let ([pos (list-find-position sym syms)]) (if (number? pos) (list-ref vals pos) (define apply-env (lambda (env sym) (env sym))) (define empty-env (lambda () (lambda (sym) (eopl:error 'apply-env "No binding for ~s" sym)))) (apply-env env sym)))))) 1. Use Scheme procedures as environments 2. Use environment datatype (define-datatype environment environment? [empty-env-record] [extended-env-record (syms (list-of symbol?)) (vals (list-of scheme-value?)) (env environment?)]) (define apply-env (lambda (env sym) (cases environment env [empty-env-record () (errorf 'apply-env "No binding for ~s" sym)] [extended-env-record (syms vals env) (let ([pos (list-find-position sym syms)]) (if (number? pos) (list-ref vals pos) (apply-env env sym)))]))) (define empty-env (lambda () (empty-env-record))) (define extend-env (lambda (syms vals env) (extended-env-record syms vals env)))
CPS • We pass an explicit continuation with each procedure call, in order to keep the code in tail- form. • How to represent continuations? Same approach we used for environments! – First implementation: A continuation is a (first-class) Scheme procedure. – Second implementation: A continuation is a record, defined using define-datatype – In both implementations: (apply-k k val)
Our First Representation of Continuations • A continuation is represented by a Scheme procedure. • The make-k procedure is used to create a continuation. •And apply-k is used to apply a continuation to an answer. • In this continuation representation, the implementations of both of these procedures are very simple.
Make-k and apply-k • (define make-k (lambda (k) k)) • Why? • (define apply-k (lambda (k v) (k v))) • Whenever we call a substantial procedure, we pass in a continuation that is “created” using make-k. – Whenever we get an answer without calling a substantial procedure, we apply the current continuation to that answer. Note that this is the same procedure as (lambda ′x ′x) These are the main principles for coding in CPS.
Live coding demo (first implementation) • The starting code is in the live-in-class folder. • The live demo is on a video (14 minutes). • View it before Thursday’s class.