Slides

Continuations Transcript

Slide 1 - Title

Hello everyone, and welcome to CS 421.

Today we are going to talk about Continuation Passing Style. Continuations are a functional representation of “what comes next” in a computer program or computation. We can pass these continuations into functions just like any other higher order function, and we can use them to do some really cool things!

Slide 2 - Objectives

Here are our objectives for today. After you have worked through the content of this video, you will be able to explain what Continuation Passing Style is, give an example of a programming technique that CPS enables, and write a recursive function using CPS. This will be a warm-up for the followup video, which will show you how to take an existing function and transform it into CPS.

Slide 3 - Direct Style

Here is some example code to consider. I have written three simple functions, inc, double, and half, that do the obvious things. Next I compose them together as inc (double (half 10)) and save the output in the variable result.

Think about what is happening when we run this. Half will receive the value 10, process it, and then return it back. The function double will continue the computation by consuming half’s output, processing it, and returning it back. Finally inc will continue the computation by consuming double’s output, processing it, and returning it back to be stored in the variable result.

Slide 4 - The Continuation

I’m sure you noticed that I talked about functions “continuing” the computation.

We can make this idea very explicit. Imagine that we take an expression and “punch out” a subexpression. In this case, let’s punch half 10 out of its containing expression. That leaves an expression with a hole in it, which we can represent using these strange looking brackets. (As an aside, these brackets are called semantic brackets. We make them by doubling up square brackets, and use them frequently when talking about transformations that work on code.)

We can call this expression-with-a-hole a context. After half 10 runs, it’s result will be placed into this context. If we turn this context into a function we can call it a continuation.

Slide 5 - Making Continuations Explicit

Let’s see how to do that now. Since a hole is just a spot where we can put a value later, we can represent that easily by wrapping it in a lambda, and letting the parameter of the lambda represent the hole. In this case, let the continuation be “lambda v inc (double v)“. For some reason it is common to name the continuation’s argument v. If the word result began with the letter v that would have worked for me, but oh well…

Now the next thing we are going to do is augment half. It will take a second parameter, which we call its continuation. By convention the continuation argument is usually given a name beginning with the letter k. At least the work continuation starts with the k sound.

When half is done, it’s going to pass its result to the continuation argument. This has a very interesting effect. When we use continuation passing style, we can imagine that the function half never returns. Remember in the higher order function lecture when we talked about using map to reduce the amount of code we needed to write? We could simply pass an operation into map and it would recurse over the list for us. A similar thing is happening here, but this time what we are parameterizing is how a function returns its value to the rest of the program.

Haskell doesn’t have this, but many languages have an explicit return keyword that functions use to exit. You can imagine what we are doing here is parameterizing the return keyword.

Slide 6 - Properties of CPS

To summarize…

We have seen two kinds of recursion so far. There is direct style, which is what most people would think of as “normal” recursion. There’s also tail recursion, or accumulator recursion, when our recursive calls happen in a function’s tail position.

Now we are going to go a step further; we are going to assume that when a function passes its result to a continuation, that the call to the continuation never returns. Because the call happens in tail position, it is likely that the bit about not returning is actually true; the compiler is going to optimize the underlying machine code to remove the intermediate stack frames.

Slide 7 - Comparisons

Let’s look at some examples. In this slide I’ve converted the rest of the functions from our initial example to use CPS. inc and double are modified similarly to half. The interesting thing is result. The continuation fed into half 10 stores the output in v1 and passes v1 into double. But now double wants a contination, so we give it one that stores double’s output into v2. Finally, we can pass v2 into inc. inc wants a continuation, but we are done, so we pass in id to make it return the result back to the main program. So notice: we often will have occasion to nest one continuation inside of another one.

Slide 8 - CPS and Imperative Style

One interesting thing people have noticed about CPS is how similar it is to imperative style. If you rewrite the functions as := style assignments you get something that looks like an imperative program.

Slide 9 - The GCD Program

Let’s look at an instance where CPS actually gets us something. This function here computes the greatest common divisor of and . It’s not particularly slow, but it does have to do a bit of modular arithmetic.

Now imagine that we wanted to take the GCD of an entire list of numbers. We can call this function GCD-star, and it’s source is on the next slide.

Slide 10 - GCD of a List

This is just a simple folding recursion. The function will traverse itself down the list until it hits the empty list, and then compute the GCD of everything as it returns.

Now imagine that there was a 1 near the beginning of the sequence. What would happen? The GCD of 1 and anything is 1, so all the GCD operations that happened on the rest of the list would be wasted. The same kind of thing happens if we wanted to take the product of all the elements of a list, and somehow there was a zero in that list. A lot of multiplications would have occured uselessly.

There are several things we could do, like checking the list first to see if there was a 1 in it, but this is a lecture about continuations, so let see how we can use continuations to fix this.

Slide 11 - Continuation Solution

I’m going to talk about this function, but you might want to pause this video and take a look at it first.

(pause)

There are a few things to notice. The first is that we have an auxiliary function aux, which uses its own continuation name newk. We initialize newk by calling aux with k. This has an interesting effect: whenever aux is running it has two separate continuations in its scope: it’s own current continuation newk, and the gcdstar-wide continuation k. There is nothing stopping aux from calling either one of these.

In the base case, aux calls newk with the base case value 0. In the usual recursive case recursive case aux creates a new continuation by wrapping a lambda around the current continuation. The parameter res will get the result of the recursive call to aux, and call gcd on that and x, and then feed that result to newk. This is just an accumulator recursion, but what we are accumulating now is a function, or a computation.

So far, so good; the magic happens on line 3, when we find a 1 in the list. Instead of calling aux recursively or using newk, we call the top-level continuation k instead. This bypasses all the computations that were in newk. No calls to gcd will happen at all in this scenario, and the auxiliary function will abort as soon as it finds a 1 in the list.

We have basically implemented exceptions using only pure functions.

Slide 12 - Other Topics

In addition to simulating exceptions, continuations can simulate multitasking. When they are used like that they are called co-routines. There are also more advanced versions of continuations, called delimited continuations. They have names like shift and reset. Finally, some languages, notably Scheme, allow you to capture the continuation of a running program with a command called call/cc.

I hope this gave you a good introduction to continuations. In the next video we’ll show you how to transform a program written in direct style into continuation passing style.