Any Questions?

Makeup Lecture

There will be a makeup lecture for the Monday section on the evening of Friday, March 28th (a week from this Friday).

Continuations

A quick review of "todo list" that is built up as one recursively descends into subexpressions during evaluation.

An example (slightly revised from that presented in lecture)

Consider the program from the PROC language
   let 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't f bound to the value VAL2? Isn't that what we bound f 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. DIFFK2 
Again, 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.

Continuations in other Languages

"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.

Programmer visible uses of continuations

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.

Abort, Exceptions

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?

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.

Example Programs

Last updated 20 March 2008.

Valid XHTML 1.0!