let
and letrec
let X = N in E1 ==> E2
, (where E2 = E1[X:=N]
)
{X
, N
, E1
} are all meta-variables.
x
, but rather to match
{X
, N
, E1
} as meta-variables
against the particular fragments of syntax.let a = 3 in -(a,2)
, we match
X
with a
,
N
with 3
, and
E1
with -(a,2)
, and
thus conclude that E2
should be -(a,2)
[a
:=3
], which
is the expression -(3,2)
transform-constprop*
, the repeated replacement
explains why you transform
let a = let b = 3 in b in proc (y) a
into proc (y) 3
:
X
=a
, N
=let b = 3 in b
, E1
= proc (y) a
},
because let b = 3 in b
is not a const expression; it is a let
expression.
X
=b
, N
=3
, E1
=b
}
E2
= b
[b
:=3
] = 3
and therefore
let a = let b = 3 in b in proc (y) a
transforms into
let a = 3 in proc (y) a
let
expression
{ X
=a
, N
=3
, E1
=proc (y) a
}
and we get E2
= proc(y) a
[a
:= 3
] = proc (y) 3
and thus the whole expression transforms into
proc (y) 3
.
proc (y) 3
as our final result.
There will be a makeup lecture for the Monday section on the evening of Friday, March 28th (a week from this Friday).
A quick review of "todo list" that is built up as one recursively descends into subexpressions during evaluation.
[@]
"if zero?(-(2,x)) then y else 4"
builds up the continuation
if [@] then y else 4 zero?( [@] ) -( [@], x )before we reach the simple expression "2"
-( [@], EXP )
is very different from the frame -( VAL, [@] )
An example (slightly revised from that presented in lecture)
Consider the program from the PROC languagelet f = proc (x) -(x,1) in let y = let f = 19 in 4 in let x = (f (f 8)) in -(y,x)
We can track how a interpreter evaluates this by tracking three variables: EXP (the expression), RHO (the environment), and KAPPA (the continuation, written as a sequence of frames, growing down).
(Question for reader: why don't we need to keep track of SIGMA in this example?)
Step EXP RHO KAPPA ======== ========================== ===================== ==================================== 1. let f = proc (x) -(x,1) [ ] == rho0 |- in let y = let f = 19 in 4 in let x = (f (f 8)) in -(y,x) 2. DOWN proc (x) -(x,1) rho0 |- <rho0, let f = [@] in let y = let f = 19 in 4 in let x = (f (f 8)) in -(y,x)> 3. ( PROC is easy: let VAL1 be the closure:<x,-(x,1),[ ]> and pass it to KAPPA ) 4. LETK let y = let f = 19 in 4 [f:VAL1]rho0 == rho1 |- in let x = (f (f 8)) in -(y,x)In step 4, we see how a frame of the form
<ENV,let VAR=[@] in EXP>
handles the value VAL it receives: it takes ENV (which we are calling
rho0
as shorthand for [ ]
in the above),
extends it with a binding of VAR to VAL
(thus extending rho0
with a
binding of f
to VAL1
;
we call this extension rho1
as a shorthand),
and then starts working on
the evaluation of EXP. All LETK
(short for "let continuation frames")
will be handled in the same manner in subsequent steps.
5. DOWN let f = 19 in 4 rho1 |- <rho1, let y = [@] in let x = (f (f 8)) in -(y,x)> 6. DOWN 19 rho1 |- <rho1, let y = [@] in let x = (f (f 8)) in -(y,x)> <rho1, let f = [@] in 4> 7. ( CONST is easy: let VAL2 be numval:19; pass VAL2 along to KAPPA ) 8. LETK 4 [f:VAL2]rho1 == rho2 |- <rho1, let y = [@] in let x = (f (f 8)) in -(y,x)> 9. ( CONST is easy: let VAL3 be numval:4; pass VAL3 along to KAPPA ) 10. LETK let x = (f (f 8)) [y:VAL3]rho1 == rho3 |- in -(y,x) 11. DOWN (f (f 8)) rho3 |- <rho3, let x = [@] in -(y,x)> 12. DOWN f rho3 |- <rho3, let x = [@] in -(y,x)> <rho3, ([@] (f 8))> 13. ( VARREF is easy: f in rho3 is VAL1; pass VAL1 to KAPPA )
(Question for reader: Why isn'tf
bound to the valueVAL2
? Isn't that what we boundf
to in the recent step 8?
14. CALLK1 (f 8) rho3 |- <rho3, let x = [@] in -(y,x)> <rho3, (VAL1 [@])> 15. DOWN f rho3 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])> <rho3, ([@] 8)> 16. ( VARREF is easy: f in rho3 is VAL1; pass VAL1 to KAPPA ) 17. CALLK1 8 rho3 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])> <(VAL1 [@])> 18. ( CONST is easy: let VAL4 be numval:8; pass VAL4 along to KAPPA ) 19. CALLK2 -(x,1) [x:VAL4]rho0 == rho4 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])>In step 19, we handle a continuation frame of the form
<(VAL [@])>
for the first time. This is an invocation: we have a value for
the operator, and a value for the operand. So if the operator
VAL1
is actually a closure value
(and it is indeed: closure:<x,-(x,1),[]>
),
we find the environment stashed away in the closure, extend it
with a binding for the formal parameter of the closure, and
then evaluate the body of the closure, removing the frame
off of KAPPA so that when the invocation finishes, it will
hand the result off to the continuation
20. DOWN x rho4 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])> <rho4, -([@],1)> 21. ( VARREF is easy: x in rho4 is VAL4; pass VAL4 along to KAPPA ) 22. DIFFK1 1 rho4 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])> <-(VAL4,[@])> 23. ( CONST is easy: let VAL5 be numval:1; pass VAL5 along to KAPPA ) 24. DIFFK2 |- <rho3, let x = [@] in -(y,x)> <(VAL1 [@])>In step 24 we handle a continuation frame of the form
<-(VAL4,[@])>
.
This is where the actual work of performing a difference
calculation occurs; we have the left and right hand values of
the subtraction, so if they are both numvals (they are), do
the subtraction
(VAL4 - VAL5
=
numval:8 - numval:1
=
numval:7
) and pass
the result numval:7
= VAL6
to KAPPA
above the frame.
25. CALLK2 -(x,1) [x:VAL6]rho0 == rho5 |- <rho3, let x = [@] in -(y,x)>In step 25 we handle a continuation frame of the form
<(VAL1 [@]>
.
This is just like step 19 above, except that instead of
invoking VAL1
on VAL4
,
we are invoking VAL1
on VAL6
.
26. DOWN x rho5 |- <rho3, let x = [@] in -(y,x)> <rho5, -([@],1)> 27. ( VARREF is easy: x in rho5 is VAL6; pass VAL6 along to KAPPA ) 28. DIFFK1 1 rho5 |- <rho3, let x = [@] in -(y,x)> <-(VAL6,[@])> 29. ( CONST is easy: let VAL7 be numval:1; pass VAL7 along to KAPPA ) 30. DIFFK2Again, like step 24, we have a difference calculating
VAL6 - VAL7
=
numval:7 - numval:1
=
numval:6
,
so pass VAL8
= numval:6
to KAPPA above this frame.
31. LETK -(y,x) [x:VAL8]rho3 == rho6 |- 32. DOWN y rho6 |- <rho6, -([@], x)> 33. ( VARREF is easy: y in rho6 is VAL3 = numval:4; pass VAL3 along to KAPPA) 34. DIFFK1 x rho6 |- <-(VAL3,[@])> 35. ( VARREF is easy: x in rho6 is VAL8 = numval:6; pass VAL8 along to KAPPA) 36. DIFFK2 |-Again, like step 24, we have a difference calculating continuation frame.
VAL3 - VAL8
=
numval:4 - numval:6
=
numval:-2
.
We pass this value to the continuation above this frame.
But there are no further continuation frames, so that means
that numval:-2
is our final answer.
"It is the evaluation of operands, not the calling of procedures, that makes the control context grow."
In some other languages (very popular ones), the implementation is allowed/encourage/specified to grow the control context on every procedure call.
You should be aware of this issue, so that you can understand when, as a client of the language specification, you may be affected by this limitation.
Remember, as a client of a specification, you are entering into a contract where you are not going to take advantage of any aspect of a particular implementation that has not included in the specification.
So if a language specification says that an implementation is allowed to grow the control context even when it is completely unnecessary, then you might be forced to structure your program in odd ways just to deal with this limitation.
So far, we have only presented continuations as a way of structuring the internals of the interpreter, making the "control context" an explicit structure, but the language we have been interpreting has not changed.
That is, whether or not continuations are explicitly modeled in the internals of the interpreter has not been a programmer visible concept. But we can expose continuations (by extending the programming language), and give the programmer more expressive power.
Handling errors in a controlled way is an important concept.
When we have failure in a program with direct input/output specification, like a compiler, then it probably makes sense to "just" abort (perhaps including some cleanup of intermediate state generated on the file system by the compiler).
But what about an interactive system, like a word processor?
How about returning special values as a way of handling exceptional cases?
lookup-birthday : PersonName -> Date
So far we have tried to avoid this by returning a distinct
value from a different domain; e.g. returning #f
when
the program normally returns a list, so that your program
would not mistake the exceptional value for the usual cases.
The main problem with this is that you end up having to write code to handle the exceptional case everywhere (especially in every recursive call), when what you would prefer as a programmer is for the exceptional case to just *locally* abort the computation, jumping directly to the place where the exception can be properly handled instead of propagating it everywhere.
call-with-current-continuation
% javac lec09_v2.java % java lec09_v2 test success: remove-1 test success: remove-2 test success: remove-3 test success: remove-4 test success: plst?-0 test success: plst?-1 test success: plst?-2 test success: plst?-3 test success: plst?-4 test success: plst?-5 test success: pstr?-0 test success: pstr?-1 test success: pstr?-2 test success: pstr?-3 test success: pstr?-4 test success: pstr?-5
Last updated 20 March 2008.