Programming Languages (CSC-302 98S)

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

# Outline of Class 23: Continuations

Held: Monday, March 30, 1998

• Welcome back! I hope you had a great braek.
• The Brown-Bag lunch series resumes today with a video on "Toward Pervasive Information Systems".
• This Friday at 11 a.m. (yes, that's right, in our class), Chris Haynes, a Scheme guy from Univ. of Indiana (one of the other UofI's), will be talking about "Recent Progress in Programming Language Design: Functional and Object-Oriented Perspectives".
• I attempted to clean my office this break (and, as you can see from looking into it, I failed miserably). It does seem that the one thing I manged to clean out were your redos for the exam. Here's an ideal answer:
Let `A` be an integer-indexed array of references to integers and `x` be an integer. If we follow the coercion rules for Algol-68 given in class (dereference the rhs sufficiently many times to obtain an appropriate type to assign to the left-hand side), then
```  A[x] = x;
```

uses the r-value of `x` on the left-hand side and the l-value of `x` on the right-hand side. Why? Because the index in the array is an integer. To get that integer, we need to dereference x (taking the r-value). However, since the value we want to store is a reference to an integer, we do not dereference the `x` on the right-hand side and therefore use its l-value. If you gave a similar answer (including explanation), let me know and you'll get full credit. If you only wrote the code and didn't give an explanation, you also got 25/25 (although I was seriously considering giving you 20/25).

### Notes on the Assignments

• If you didn't print out your assignment, I may not have graded it.
• I'll try to do so in the near future.
• Since I do my grading on paper, it makes it much easier for me if you print out your homeworks
• I only graded the first four questions on assignment three. Why?
• Because it's already a little bit late (so I don't think grading all of the will provide a great benefit)
• Because problem 5 is likely to have similar errors to problems 3 and 4.
• Few of you did well on the How might you convince them that their reasoning is misguided? part of question three on assignment three. Why?
• Because you didn't react to the criticisms but instead gave independent (and therefore irrelevant) positive attributes of user-definable types.
• For example, many of you said something on the order of "User-definable types complicate and slow down the compiler. However, user-definable types support further levels of abstraction." Someone who cares about compiler speed isn't necessarily going to be swayed by levels of abstraction.
• In some ways, your answers seemed like the following dialogue about Grinnell: "I wouldn't go to Grinnell because it's in a small town." "Yeah, but Grinnell has an open curriculum."
• What would I have liked to see? Something on the order of
Yes, user-definable types may slow down compilation. Note, however, that the slowdown won't be in the type-checking, which should not be more complicated with additional types (in many cases, it's simply a lookup in a table), particularly if we don't allow coercion of user-definable types and use a restricted version of type equivalence.
In addition, it's important to consider why you object to a slower compiler. Note that compilation speed is just one factor in the time of program development, which also includes time for testing, debugging, and development. Since user-definable types have the capability of increasing the readability and modularity of a program, they are likely to slightly decrease writing time (as programmers won't need to continually remind themselves of the different variables they've chosen to use a compound type) and significantly decrease debugging time. Relative to these gains, the slight loss in compilation speed is not a signficant factor.
You are correct that it will be more complicated to build the compiler. However, that is only of concern to implementers. If you are an implementer of the language, you might want to consider some of the advantages you encounter from user-definable types. First, such types are popular, so you increase the salability of your compiler. More importantly, user-definable types give you (the compiler writer) some control over how data are organized within the type, so you can potentially provide more efficient implementations. I realize these don't necessarily ameliorate your additional costs, but you must understand that since your compiler is likely to be used by thousands (if not hundreds of thousands) of programmers, a large increment in compiler complexity is often considered worthwhile even if it gives only a small benefit to programmers, because that small benefit is multiplied by thousands.
• I also found the testing of your sorting routines to be minimalist at best. We'll be doing an exercise on testing sorting routines today. (Yes, it will be related to Scheme).

## Exercise: Writing Testing Routines

In groups of approximately four, write a testing predicate for the quicksort routine that we developed before break. Your predicate should return true if the sorting routine can sort any list of integers up to size fifteen and false if it cannot sort any such list. Your routine should be sufficiently robust that you'd be willing to stake your grade on it (that is, that any sorting routine that passes the predicate will correctly sort any list of integers of size less-than or equal to fifteen and that any sorting routine that fails the predicate will fail on at least one such list).

## Continuations

• An interesting aspect of Scheme is that it gives you access to what are typically called continuations. Continuations are functions that, in effect, represent "the rest of the computation".
• What do we mean by "the rest of the computation". Consider the expression
```(* 3 (- (+ A 4) 5))
```

• As you know, this is executed by
• subtracting 5 from the result
• multiplying 3 by the result of that computation
• So, the continuation of `(- (+ A 4) 5)` is "multiply 3 by the result".
• Similarly, the continuation of `(+ A 4)` is "subtract 5 from the result and then multiply 3 by that new result".
• Observe that we can write each of these as a lambda expression.
• "multiply 3 by the result":
```(lambda (x) (* 3 x))
```

• "subtract 5 from the result":
```(lambda (y) (- y 5))
```

• We can use these continuations to rewrite the original expression as
```((lambda (x) (* 3 x))
((lambda (y) (- y 5))
(+ A 4)))
```

• Why is this useful? In part, it makes the order-of-evaluation somewhat clearer (albeit backwards). First you add A and 4, then you subtract 5, then you multiply by 3.
• Note that this is also getting fairly close to assembly code (again, backwards assembly code). Compare it to
```ADD A #4
STO TMP
SUB TMP #5
STO TMP
SUB #3 TMP
```

• In fact, Scheme compilers always build such continuations as they compile Scheme programs (I believe that Scheme interpreters also build such continuations).
• It provides you with greater control over sequencing. Consider `((read) - (read))`. Which `(read)` is done first? It turns out that it's up to the implementer. If you want the first one done first, you could write
```((lambda (first)
((lambda (second) (- first second))
```

• In gives you a different way of thinking about programming. As we'll see later, continuations can provide convenient mechanisms for writing recursive functions and for providing "functionally pure" input and output.
• In Scheme, you can get access to your current continuation. This permits some very interesting control possibilities which we will also discuss later.
• It lets you strengthen your understanding of lambda expressions.

### Exercise

You should work on this exercise in groups of approximately four. Turn in a sheet with your answers.

As you may know, one of the criticisms of recursive program design is that it is "inefficient" because of the space required for stack frames and the time required to add and remove those stack frames. One solution to this problem is to use tail-recursive functions in which "nothing" is done with the result of the recursive call (other than returning it). With such functions, it is possible to replace the contents of the stack frame (updating any parameters with their new values) rather than to create a new stack frame.

Consider the factorial function, typically written as

```(define (fact n)
(if (= n 0) 1
(* n (fact (- n 1)))))
```

One way to make this tail-recursive is to define a helper function that takes a second accumulator as a parameter and have that accumulator accumulate the multiplication of `n*(n-1)*(n-2)*...`. Such a function might look like

```(define (tr-fact n)
(tr-fact-helper n 1))
(define (tr-fact-helper n acc)
(if (= n 0) acc
(tr-fact-helper (- n 1) (* n acc))))
```

There are a few problems with this strategy. First, it is not sufficiently general (not all recursive functions can use a numerical accumulator). Second, it does not do the operations in the same order (the original version multiplied 1*1*2*3*...*n. This multiplies 1*n*(n-1)*...1.).

To develop a more general strategy for making recursive functions tail-recursive, we can rely on continuations. Rather than passing an accumulator in each recursive call, we'll instead pass a continuation that describes what to do with the result. Here's a sketch

```(define (cfact n)
(cfact-helper n (lambda (x) x)))
(define (cfact-helper n cont)
(if (= n 0)
(cont 1)
(cfact-helper (- n 1) ???)))
```

Your goal is to figure out what to plug in for the `???`. How can you do so? You might first determine what happens when you call `(cfact 0)`. Next, consider what you know about continuations. For example, in the original definition of factorial, if the continuation for ```(fact 5)``` is C, what is the continuation for ```(fact 4)```?

Run the new, continuation-based `(cfact 3)` by hand to see what happens. What do you observe?

Can you use this strategy for other common recursive functions? Why or why not?

On to More Topics in Scheme
Back to Other Issues in Functional Languages and Scheme
Outlines: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
Current position in syllabus

[Instructions] [Search] [Current] [Changes] [Syllabus] [Handouts] [Outlines] [Assignments]

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.