TWiki . CS590S . LectureNotes4 TWiki . { Main | CS590S | TWiki }
CS590S . { Home | Users | Changes | Index | Search | Go }

Continuation-Passing Style

Continuation-passing style or CPS is a technique for implementing functional programs in which procedures are written so that they receive procedural arguments representing their future behavior -- these arguments are called continuations, or equivalently continuations are functions that "embody the rest of the computation". A procedure in CPS form does not return a value, rather it calls one of its continuations. Thus one way to look at CPS is as a restricted language in which there are no returns, only labeled gotos. Continuation passing style makes the control flow of programs more explicit as every procedure has the power to change the execution of the remainder of the program, contrast this to the traditional model in which procedures have no control over the behavior of the program once they return to their caller.

Rabbit and Orbit are examples of scheme compilers based on CPS conversion, which are and have been state of the art for the last twenty years. [See references]

Metacircular interpreters, the term comes from Mc Carthy's implementation of Lisp in Lisp, are interpreters for CPS written of CPS. Metacircular interpreters are useful to understand the semantics of a language -- but they do not provide a full definition of that language nor a viable implementation. They are fun though.

A good reference to CPS and functional programming can be found in the book "Essentials of Programming Languages by Friedman, Wand and Haynes, see LectureNotes3 for a reference.

Why CPS?

Why should we transform programs to CPS? Some the advantages of CPS are:

  1. tail recursion elimination: function calls in tail position can be replaced by labeled jumps (cf Steele), thus turning recursion into iteration. This has the advantage of not growing the stack and simplifying the semantics of procedure calls by eliminating returns Some languages, Scheme in particular, mandate tail recursion elimination. Thus all implementations of the language must implement this optimization.
  2. simplicity: CPS is a simple intermediate representation
  3. remove some of the complexity inherent to high level program structures: expose optimization opportunities for compilers by simplifying control flow
  4. eliminate nested expressions: (e.g. f(g(h(),i()))), all intermediate expressions are named in CPS, which allows the compiler to make evaluation strategies explicit at CPS transformation time. The result is very close to SSA.

Naive CPS Transform

An informal algorithm for CPS transformation must apply the following transformations:

A simple example of translation is the following:

       (f(g x))    ==>  (g (\v.f k v)) x
                                 *--------------f's continuation
                           *********------------g's continuation

An example in ML

Please bear with me, I have last written ML code in 1992 when translating a Prolog program that performed abstract interpretation of Postcript into ML for performance reasons. I have not touched either language since. So the syntax and semantics of this example are highly suspect.

Consider a map function:

   fun map f []   = []

    |  map f x::l = (f x):: map f l

An example of the use of this function is
(map (\x. x + 1) 1::2::1::[])
which yields the list
2:3::2::[]

Translation to CPS

We are going to translate map to CPS in three step, the final result will be a function map'.

step 1:

fun map' k f []   =  k [] 

 |  map' k f x::l =  k ( ...

step 2:
fun map' k f []  = k [] 

 |  map' k f x::l =  f (\v.(k (v:: map' f l)) x)

step 3:
fun map' k f []  = k [] 

 |  map' k f x::l =  f 

                    ((\v.(map' f (\v'. k ( v::v')))) l)
                    x

To conclude lets look at an example application where f is f = (\x.x+1) and the expression to evaluate is map f 1::[].

The functions are CPS converted to:

f'   = (\k\x.k (x+1))
map' k f []   = k [] 

 |   k f x::l = f (\v.map' f (\v'. k (v::v')) l) x

the expression is thus translated to map' I f 1::[] and it evaluates as follows:

        f' (\v.map' f' (\v'.I(v::v') []) 1 
   ==>
        (\k\x.k(x+1)) (\v.map' f' (\v'.I(v::v')) []) 1 
   ==>
        (\v.map' f' (\v'.I(v::v')) []) (1+1) 
   ==>
        (\v.map' f' (\v'.I(v::v')) []) 2
    ==>
        map' f' (\v'.I(2::v')) [] 
   ==>
        (\v'.I(2::v')) [] 
   ==>
        I 2::[] 
   ==>
        2::[]


When do we lift expressions?
        + out of applications
        + out primitive calls
        + out cond tests


        - from lambdas
        - lift out of branches


C: Exp X Cont -> Exp
C[[x]]k = k x
C[[\x.e]] = k (\k'\x. C[[e]]k')

that's all folks...

@article{kelsey98revised,
   author = "Richard Kelsey and William Clinger and Jonathan Rees (Editors)",
   title = "Revised^5 Report on the Algorithmic Language Scheme",
   journal = "ACM SIGPLAN Notices",
   volume = "33",
   number = "9",
   year = "1998"}


@inproceedings{kranz86,
   author = {David Kranz, Richard Kelsey, Jonathan Rees, Paul Hudak, James Philbin, and Norman Adams},
   title = {ORBIT: An optimizing compiler for Scheme},
   note  = {In Proc. of the SIGPLAN '86 Symposium on Compiler  Construction}, 
   pages = {219--233}, 
   publisher = ACM, 
   year = 1986,
   month = June}


@phdthesis{steele78,
   author = {Guy Lewis Steele, Jr},
   title = {RABBIT: A Compiler for SCHEME}
   institution = {Masters Thesis. MIT AI Lab. AI Lab Technical Report AITR-474}, 
   month =May,
   year = 1978}
-- JanVitek - 30 Aug 2002

Topic LectureNotes4 . { Edit | Ref-By | Attach | Diffs | r1.4 | > | r1.3 | > | r1.2 | > | r1.1 }
Revision r1.4 - 01 Sep 2002 - 02:46 by JanVitek Copyright © 2000 by the contributing authors. All material on this collaboration tool is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback.