ArticlePDF Available

Call with current continuation patterns

Authors:

Abstract

This paper outlines the recurring use of continuations. A brief overview of continuations is given. This is followed by several patterns that outline the use of continuations leading up to using continuations to implement coroutines, explicit backtracking, and multitasking. Scheme is used for the examples as it supports first class continuations.
Call with Current Continuation Patterns
Darrell Ferguson Dwight Deugo
August 24, 2001
Abstract
This paper outlines the recurring use of continuations. A brief overview of continuations
is given. This is followed by several patterns that outline the use of continuations leading
up to using continuations to implement coroutines, explicit backtracking, and multitasking.
Scheme is used for the examples as it supports first class continuations.
1 Introduction
We often find ourselves in a situation while researching a selected topic (say the use of
continuations in Scheme) where we have found a paper that deals with the topic and read a
portion of the paper only to realize that our knowledge is not extensive enough to understand
all the material. So, we stop reading the paper and begin reading the papers that are used
as references and other material that gives us some background knowledge of the subject.
After reading the background work, we return to read the rest of the original paper. Well,
just as we packaged up our reading of the first paper and returned to it at a later time,
continuations allow for the same ability in programming.
Although they do not fit into what most people see as standard programming (procedu-
ral) and they are foreign to most programmers, continuations allow for a variety of complex
behaviours without modification to the interpreter or compiler. Powerful control structures
can be implemented using only continuations including multitasking, backtracking, and
several escape mechanisms.
Unfortunately the concept of first class continuations (continuations that can be passed
as arguments, returned by procedures, and stored in a data structure to be returned to later)
does not exist in most languages. This is why Scheme [Dyb96] was used as the language
for our examples of the usage of continuations.
We will begin by providing a brief introduction to continuations for those who are not
familiar with them. In the next section we will describe two concepts - contexts and escape
procedures- that will allow us to understand continuations. We will then show how con-
tinuations can be made from these two concepts. We will then give several patterns which
School of Computer Science, Carleton University. darrell.ferguson@ottawa.com
deugo@scs.carleton.ca . Copyright c2001, Darrell Ferguson and Dwight Deugo. Per-
mission is granted to make copies for purposes related to the 2001 Pattern Languages of Programs
Conference. All other rights reserved.
1
demonstrate the recurring use of continuations ending with their use in coroutines[HFW86],
explicit backtracking [FHK84] [SJ75] and multitasking [DH89].
1.1 Continuations
We will begin our discussion of continuations by defining the context of an expression as
well as escape procedures. Our examples are taken from [SF89] and more information
about these two concepts can be seen there.
1.1.1 Contexts
In [SF89] the context is defined as a procedure of one variable, . To obtain the context
of an expression we follow two steps; 1) replace the expression with and; 2) we form a
procedure by wrapping the resulting expression in a lambda of the form (lambda ( ) ... ).
For example, we can take the context of (+ 5 6) in (+ 3 (* 4 (+ 5 6))) to be:
(lambda ( )
(+3 (* 4 )))
We can extend this by extending the first step of context creation. We can evaluate the
expression with and when evaluation can no longer proceed (because of ), we will have
finished the first step. To demonstrate this we will look at finding the context of (* 3 4) in:
(if (zero? 5)
(+ 3 (* 4 (+ 5 6)))
(* (+ (* 3 4) 5) 2))
We begin by replacing (* 3 4), giving us:
(if (zero? 5)
(+ 3 (* 4 (+ 5 6)))
(* (+ 5) 2))
Continuing with the first step, we evaluate (zero? 5) to be false and choose to calculate the
alternative part of the if statement. This gives us (* (+ 5) 2). No more computation can
take place so we continue to the second step and the context becomes:
(lambda ( )
(* (+ 5) 2))
1.1.2 Escape Procedures
The escape procedure is a new type of procedure. When an escape procedure is invoked,
its result is the result of the entire computation and anything awaiting the result is ignored.
2
The error procedure is an example of an escape procedure where everything that is waiting
for the computation is discarded and an error message is immediately reported to the user.
Now let us assume that there exists a procedure escaper which takes any procedure as
an argument and returns a similarly defined escape procedure [SF89]. An example of the
escaper procedure would be:
(+ ((escaper *) 5 2) 3)
The expression (escaper *) returns an escape procedure which accepts a variable number of
arguments and multiplies these arguments together. So, when ((escaper *) 5 2) is invoked,
the waiting + is abandoned (due to the escape procedure) and 10 is returned [the result of
(* 5 2)].
In Escape from and Reentry into Recursion we will see a definition of the escaper
function that is made using continuations.
1.1.3 Defining Continuations
The procedure call-with-current-continuation (which is usually shortened to call/cc) is a
procedure of one argument (we will refer to this argument as a receiver). The receiver must
be a procedure of one argument which is called a continuation.
The call/cc procedure forms a continuation by first determining the context of (call/cc
receiver) in the expression. The escaper procedure shown in the previous section is then
invoked with the context as an argument and this forms the continuation. The receiver
procedure is then invoked with this continuation as its argument.
Example
If we take for example the expression:
(+ 3 (* 4 (call/cc r)))
The context of (call/cc r) is the procedure:
(lambda ( ) (+ 3 (* 4 )))
So the original expression can be expanded to:
(+ 3 (* 4 (r (escaper (lambda ( ) (+ 3 (* 4 )))))))
If we consider rto be (lambda (continuation) 6) the above works out to be:
(+ 3 (* 4 ((lambda (continuation) 6)
(escaper (lambda ( ) (+ 3 (* 4 )))))))
3
The continuation (and escape procedure) is never used so:
((lambda (continuation) 6) (escaper (lambda ( ) (+ 3 (* 4 )))))
results in 6 and the entire expression returns 27 (3 + 4 * 6). However if rwas (lambda
(continuation) (continuation 6)), we would have:
(+ 3 (* 4 ((lambda (continuation) (continuation 6))
(escaper (lambda ( ) (+ 3 (* 4 )))))))
Since we invoke continuation on 6, we have:
((escaper (lambda ( ) (+ 3 (* 4 )))) 6)
Remember that the escaper procedure returns an escape procedure which abandons its con-
text (so the + and * waiting for the result of evaluating the escaper function are discarded).
Although this still results in 27, the important point is the process of getting the result has
been changed.
4
2 The Patterns
Escape from a Loop
This pattern demonstrates the separation of code into a control structure (looping mech-
anism) and action (what to perform during each loop). Continuations are used for breaking
out of the control structure. We use call/cc and a receiver function to get the escape proce-
dure before entering the loop. Now the body of the loop has access to a function which will
escape its context, breaking the loop.
Context
We have a system that can be looked at as a combination of two constructs, the control
(looping mechanism) and action (what to perform during each loop).
Problem
How do we break from our looping mechanism?
Forces
Delegation of looping to a single reusable function allows for code reuse but
stopping could be complicated.
Copying the looping mechanism and merging it with each action results in
duplicating code.
Attempting to check the return value inside the loop mechanism each time
the action is performed looking for a special return value to signal stopping
the loop restricts what the function can return. If we change this special
return value we must change every function that is passed into it.
We want to keep knowledge of how the looping mechanism works away
from the action to ensure that these parts are independent of each other.
Solution
We can use a continuation to store the context before entering the loop and use it as
an escape procedure when the exit condition is met. We begin our procedure by creating
a receiver procedure for a continuation (which we will use as our escape procedure). This
receiver procedure will invoke the procedure that contains the looping mechanism with the
action procedure to be invoked at each step passed in as an argument. This action procedure
will be defined within the receiver procedure to enable access to the exit-procedure (due
to the lexical scoping). Terminating the infinite loop can be accomplished by invoking
the escape procedure (the continuation) inside the single action procedure with whatever
exiting value is wanted when the exiting condition(s) is/aremet.
Sample Code
An infinite loop can be made by creating a procedure that takes a procedure as an
argument. This function would have a locally defined looping function that 1) calls the
5
original procedure passed in; and 2) recursively calls itself. We could also place any code
that should be executed during each loop (before or after the action) in this procedure. With
these parts shown in italics, the looping procedure would be:
(define infinite-loop
(lambda (procedure)
(letrec ((loop (lambda ()
... code to execute before each action ...
(procedure)
... code to execute after each action ...
(loop))))
(loop))))
We will now look at the structure of an action procedure with the parts of the procedure
to be filled in shown in italics.
(define action-procedure
(lambda (args )
(let ((receiver (lambda (exit-procedure)
(infinite-loop
(lambda ()
(if exit-condition-met
(exit-procedure exit-value )
action-to-be-performed )))))))
(call/cc receiver))))
Example
Using the definition of infinite-loop with no code added before or after the action, we
can create a procedure which counts to n(displaying each number it counts). At n, the
function will escape the loop and return n.
(define count-to-n
(lambda (n)
(let ((receiver (lambda (exit-procedure)
(let ((count 0))
(infinite-loop
(lambda ()
(if (= count n)
(exit-procedure count)
(begin
(write-line ”The count is: ”)
(write-line count)
(set! count (+ count 1))))))))))
(call/cc receiver))))
6
Rationale
We have now broken up our procedure into two parts, the control (loop) and the action.
Using call/cc, we have a way of stopping the infinite loop. Since our looping mechanism
has been separated from our action we are able to use the same looping mechanism with
different action procedures and we can add default behaviour to be performed during each
iteration of the loop without having to modify each action procedure.
7
Escape from Recursion
This pattern presents a solution to escaping from a recursive computation. Similar to
the Escape from a Loop , we create an escape procedure in the context before entering the
recursive computation and use this procedure if a break condition is met.
Context
We are writing a recursive procedure. Our domain makes it possible that we will know
the final result without completing the computation.
Problem
How do we escape from a recursive computation?
Forces
Exiting out of a computation (discarding the computation that has been built
up) will make the procedure extremely efficient for special cases (possibly
avoiding all computation).
You may lose readability by adding complexity to the procedure.
Building a recursive computation will also temporarily build up stack space
which can normally be avoided with a language that supports tail-recursion.
Solution
We can use call/cc and a receiver function to create an escape function with the context
before any of the computation is recursively built up. We can then perform the normal
recursive function using a recursive helper function defined within the receiver function
(to provide access to the continuation). If the special condition is met, the continuation is
invoked with the desired exit value.
Sample Code
We will now look at the structure of a recursive procedure using call/cc to exit out of
the recursive computation. The portions of the procedure to be filled in are shown in italics.
(define function
(lambda (args )
(let ((receiver
(lambda (exit-procedure)
(letrec ((helper-function
(lambda (args )
(cond
(break-condition (exit-function exit-value ))
other cases and recursive calls
(helper-function args )))))
(call/cc receiver))))
8
Examples
If we need to compute the product of a non-empty list of numbers, we know that if 0
occurs in the list the product will be 0. So, we can write product-list as:
(define product-list
(lambda (nums)
(let ((receiver
(lambda (exit-on-zero)
(letrec ((product
(lambda (nums)
(cond
((null? nums) 1)
((zero? (car nums)) (exit-on-zero 0))
(else (* (car nums)
(product (cdr nums))))))))
(product nums)))))
(call/cc receiver))))
If we take (product-list ’(1 2 3 0 4 5)) we get:
(product ’(1 2 0 3 4))
(* 1 (product ’(2 0 3 4)))
(* 1 (* 2 (product ’(0 3 4))))
And at this point, the exiting condition is met and we exit from the computation and return
0. Notice that the waiting multiplications were discarded.
We can apply this to deep recursion (tree recursion) as well. If we allow the list of
numbers to contain lists of numbers and so on, we can rewrite product-list to be:
(define product-list
(lambda (nums)
(let ((receiver
(lambda (exit-on-zero)
(letrec ((product
(lambda (nums)
(cond
((null? nums) 1)
((number? (car nums))
(if (zero? (car nums))
(exit-on-zero 0)
(* (car nums)
(product (cdr nums)))))
(else (* (product (car nums))
(product (cdr nums))))))))
(product nums)))))
(call/cc receiver))))
9
Rationale
Using call/cc to escape from the recursive process adds little code to the original recur-
sive procedure. It also allows us to discard much of the unnecessary computation (some-
times all of the computation can be avoided).
Consequences
A language that supports tail-recursion will allow you to write recursive functions that
do not build up the stack (tail-recursive procedures). A tail-recursive procedure will keep
a running result of the computation. This means that this pattern has little effect when
escaping from a tail-recursive procedure. However, if the computation at each recursive
call is expensive and the procedure will not build up a huge computation stack, this pattern
can be used as an alternative to tail-recursion.
10
Loop via Continuations
This pattern presents a solution to creating a loop using continuations. We use call/cc
to get an escape procedure which will return to the context of the beginning of the loop
body. We can then use this escapeprocedure to escapefrom the current context back to the
beginning of the body which we want to loop over.
Context
Although the Escape from a Loop pattern deals with escaping from an infinite looping
mechanism, we can also face the situation where we want the ability to loop over a portion
of a procedure or computation (which may be separated over portions of several procedures)
until some condition is met.
Problem
How do we loop until condition is met using continuations?
Forces
Other existing looping mechanisms such as the for or while loops are more
common and are what programmers are used to.
The existing for and while loops have to be contained within one procedure.
Separating the mechanism for looping over several procedures may make the
program more difficult to read.
We may be able to write our program quicker or more easily by writing the
code to perform one iteration without worrying about looping and adding the
loop mechanism later.
Looping can be added to portions of existing code with minimal effort and
changes to the existing program.
Solution
We can create a loop by getting the continuation at the beginning of the portion of
the procedure to be looped over and storing it in a temporary variable. If we determine that
looping becomes necessary, we simply invoke the continuation. This will escape the current
context and return to the beginning of the loop portion. Getting a handle on a continuation
can be done easily with the identity function using the line (call/cc (lambda (proc) proc)).
For terminating the loop we will need some way of changing the state of the execution
from a state which will continue the loop to one that will not (ie. the loop-condition becomes
false). This can be done by the manipulation of variables which are defined outside of the
loop. We can then use assignment (set!) to change this value. Another approach would be
to add the current values to the continuation’s argument.
When the portion of code to be looped over is separated amongst several procedures
where we want to exit out of one of these proceduresand start the loopover, we can simply
11
pass the continuation along as an argument. Inside these functions, if the condition to
return to the beginning of the loop is met, we simply invoke the continuation with itself as
a parameter.
Sample Code
We will now look at the structure of the loop using call/cc . The portions of the proce-
dure to be filled in are shown in italics.
(define partial-loop
(lambda (args )
... preliminary portion ...
(let ((continue (call/cc (lambda (proc) proc))))
... loop portion ...
(if loop-condition
(continue continue)
... final portion ... )))
The reason for invoking (continue continue) to loop is tricky. If we remember how call/cc
works, we first get the context of the call/cc call. Here, that would be:
(lambda ( )
(let ((continue ))
... loop portion ...
(if loop-condition
(continue continue)
... final portion ... )))
Examining this, when we invoke (continue **some argument**) , whatever argument
we apply will now become the value of continue . Since we are attempting to loop by
continually returning to this point of execution it seems logical that we do not wish for this
value to change so we pass the previous value of continue (which is our continuation) to be
rebound to continue allowing us to repeatedly return to the same point of execution.
Example
Here is an example where we are given a non-empty list of numbers and we want to
increment each element of the list by 1 until the first element is greater than or equal to
100. We will separate out the initial construction of the continuation to a separate function
(labelled get-continuation-with-values ) for readability.
12
(define get-continuation-with-values
(lambda (values)
(let ((receiver (lambda (proc) (cons proc values))))
(call/cc receiver))))
(define loop-with-current-value
(lambda (values)
(let ((here-with-values (get-continuation-with-values values)))
(let ((continue (car here-with-values))
(current-values (cdr here-with-values)))
(write-line current-values)
(if ( (car current-values 100))
(continue (cons continue
(map (lambda (x) (+ x 1))
current-values)))
(write-line ”Done!”))))))
If we attempted (loop-with-current-value ’(1 2 3)), the first value of here-with-values
would be the list (*continuation* 1 2 3). We then separate this into continue being *con-
tinuation* and current-values being ’(1 2 3). The first time we execute the line (continue
(cons continue (map (lambda (x) (+ x 1)) current-values))), we would be rebinding here-
with-values to be (*continuation* 2 3 4). We then hit that line againand go back to rebind
here-with-values to (*continuation* 3 4 5). This continues until we get the here-with-
values bound to (*continue* 100 101 102) at which point we write the string ”Done!” to
the screen and exit.
Rationale
The use of continuations to create the looping mechanism adds little code to create
the loop and with a few comments in the code saying how the continuations are being
used (to return to the beginning of the loop body) the program remains readable. With the
code broken up into procedures, code can be reused in different loops that use this looping
mechanism (passing in their own continuation to these procedures).
13
Escape from and Reentry into Recursion
This pattern presents a solution to the problem of escaping from a recursive process
while allowing for the process to be reentered. We create a break procedure which will
first store the current continuation in a specified scope and then escape from the recursive
process. Invoking the stored continuation will continue the recursive process.
Context
We have a recursive computation (possibly deep or tree recursion) which we want to
escape from but keep the ability to go back and continue the execution. This will allow for
a programmer to debug his/her code by determining when special conditions are met. We
can also use this tool to change the internal values of the function or possibly some external
variables as the computation is progressing so that we can change the computation on the
fly. For this pattern, the reentering of the computation can be instigated by the user.
Problem
How do you escape from a recursive computation while allowing for the ability to reen-
ter at the point of exit?
Forces
We want to minimize the amount of code modified and added while keeping
the code readable. This will allow for this feature to be removed easily if it
is being done for debugging purposes.
We want to avoid modifications to the interpreter/compiler in adding this
ability (we do not want a full debugging facility).
Solution
We will use for our solution, the escaper procedure from [SF89] to escape the current
computation and call/cc to store the context that we are breaking from so that it can be
reentered. We will need a break procedure as well as a resume-computation procedure,
both of which will need to be globally accessible. The break procedure should do two
things; 1) store the current continuation and 2) stop the current computation. The break
procedure can also take in a value which will be passed in to the continuation when it is
resumed. The resume-computation procedure will, as the name says, resume the currently
escaped computation. If we have these functions it should be a simple matter of insterting
a break when we wish to halt execution.
In creating the break procedure we will begin by looking at the second requirement.
We have already discussed escape procedures and [SF89] gives a definition of the escaper
procedure. We can use this procedure to escape the current computation. As for first part of
the break, we need to begin by getting the current continuation (done simply through the use
of call/cc with a receiver function). Looking ahead at the resume-computation procedure,
we can see that invoking this procedure should invoke the continuation that break stored
with the argument that was passed in originally. So, we can have break set the resume-
computation procedure to be (lambda () (continuation arg)).
14
Sample Code
The break procedure would be:
(define break
(lambda (arg)
(let ((exit-procedure
(lambda (continuation)
(set! resume-computation (lambda () (continuation arg)))
(write-line ”Execution paused. Try (resume-computation)”)
((escaper (lambda () arg))))))
(call/cc exit-procedure))))
However, we will need to initialize resume-computation to some temporary procedure
(which will be overwritten the first time that break is invoked). So we will define it to be
(lambda () ”to be initialized”).
We will now create the escaper procedure mentioned in Section 1.1.2. To begin, we will
need to store a continuation (which we will call escape-thunk) with the context (lambda
() ( )). This can be done by first assigning it to some procedure:
(define escape-thunk (lambda () ”escape-thunk Initialized”))
We then need to create a receiver function for a continuation which will assign the contin-
uation to be our escape-thunk.
(define escape-thunk-init
(lambda (continue)
(set! escape-thunk continue)))
And we simply need to create the continuation using call/cc with this receiver function.
((call/cc escape-thunk-init))
The escaper procedure can then be defined as:
(define escaper
(lambda (procedure)
(lambda args
(escape-thunk (lambda () (apply procedure args))))))
Example
An example of this pattern would be the flatten-list procedure below which will escape
every time a non-list argument is passed to the function.
15
(define flatten-list
(lambda (arg)
(cond ((null? arg) ’())
((not (list? arg)) (list (break arg)))
(else (append (flatten-list (car arg))
(flatten-list (cdr arg)))))))
And the output of running (flatten-list ’(1 2 3)) will be:
1 ]= (flatten-list ’(1 2 3))
”Execution paused. Try (resume-computation)”
;Value: 3
1 ]= (resume-computation)
”Execution paused. Try (resume-computation)”
;Value: 2
1 ]= (resume-computation)
”Execution paused. Try (resume-computation)”
;Value: 1
1 ]= (resume-computation)
;Value 3: (1 2 3)
16
Coroutines
Coroutines allow for sequential control between procedures. Using call/cc, we obtain
the current context of a procedure and store it so it can later be invoked.
Context
The process of many programs can be viewed as the passing of control sequentially
among several entities. A good analogy is to compare this to many traditional card games
where each player takes his/her turn and then passes control to the next person.
Problem
How do we allow sequential control among several entities?
Forces
Having each entity’s process in one procedure allows us to view each process
in one place.
Breaking each entity’s process up into many small procedures that force the
sequential control becomes difficult to trace through for the programmer and
difficult to change the process of control.
Using a mechanism such as threads and semaphores may have a high over-
head or may not be available.
The switching of threads is traditionally done at a lower level and to design
the threads to wait until called upon to act would be complex and hard to
follow.
Solution
We can use continuations to implement coroutines. Coroutines allow for interruption
of a procedure as well as resuming an interrupted procedure. We will begin by creating
a coroutine for each entity that will share control. The coroutine-maker (as defined in
[SF89]) will take as its argument a procedure that represents the body or actions of the
entity. This body-procedure will take two arguments, a resumer procedure that will be used
to resume the next coroutine in order and an initial value.
The coroutine-maker will create a private update-continuation function to be used to
store the current continuation of the procedure. Each time the coroutine is invoked with a
value, it passes that value to its continuation. When a coroutine passes control to another
coroutine, it updates its current state to its current continuation and then resumes the second
coroutine.
17
Sample Code
The coroutine-maker from [SF89] is:
(define coroutine-maker
(lambda (proc)
(let ((saved-continuation ’()))
(let ((update-continuation! (lambda (v)
(write-line ”updating”)
(set! saved-continuation v))))
(let ((resumer (resume-maker update-continuation!))
(first-time #t))
(lambda (value)
(if first-time
(begin(set! first-time #f)
(proc resumer value))
(saved-continuation value))))))))
As you can see, coroutine-maker makes use of a helper function resume-maker. Again
from [SF89], resume-maker will be defined as:
(define resume-maker
(lambda (update-proc!)
(lambda (next-coroutine value)
(let ((receiver (lambda (continuation)
(update-proc! continuation)
(next-coroutine value))))
(call-with-current-continuation receiver)))))
Example
A simple example of coroutines that make use of continuations can be found in [SF89].
A more advanced mechanism and several extensions to coroutines can be found in [HFW86].
Using the coroutine-maker from [SF89] we can create two procedures labelled ping
and pong which will switch control back and forth three times. The ping-procedure and
pong-procedure will be the body-procedures (used by the coroutine-maker to create the
coroutines) for ping and pong respectively.
(define ping
(let ((ping-procedure (lambda (resume value)
(write-line ”Pinging 1”)
(resume pong value)
(write-line ”Pinging 2”)
(resume pong value)
(write-line ”Pinging 3”)
(resume pong value))))
18
(coroutine-maker ping-procedure)))
(define pong
(let ((pong-procedure (lambda (resume value)
(write-line ”Ponging 1”)
(resume ping value)
(write-line ”Ponging 2”)
(resume ping value)
(write-line ”Ponging 3”)
(resume ping value))))
(coroutine-maker pong-procedure)))
And the output of running (ping 1) will be:
1 ]= (ping 1)
”Pinging 1”
”Ponging 1”
”Pinging 2”
”Ponging 2”
”Pinging 3”
”Ponging 3”
;Value: 1
Rationale
The use of continuations to create coroutines allow us to write the process for each
entity in one procedure which makes the program more readable and easier to modify. The
use of continuations enable us to achieve the same result as threads without modification to
the programming language or interpreter.
19
Non-blind Backtracking
This pattern addresses the issue of allowing us to move to a previous or ”future” point
of execution. Continuations are stored for past and future points and ”angel” and ”devil”
procedures are given which allow us to move to a future point of execution or a past point
respectively.
Context
We have reached a point in the computation where we wish to stop or suspend the
current process and jump to a previous point in the computation.
Problem
How do we construct a mechanism which will allow us to get back to a past point in
execution and follow another path to determine the solution?
Forces
We need a simple mechanism for moving back and forth in a computation
that is robust enough to handle situations where it may not be enough to
simply go back and forth.
Modifying or redesigning a program to incorporate the mechanism should
be simple and not add too much complexity to the existing code that would
make it unreadable.
Designing the system to achieve a form of backtracking through breaking the
process into recursive process that achieve backtracking by returning back to
the calling procedure would be inefficient if we want to return to a point that
was reached very early in the computation. This would also be very difficult
to follow for the programmer for complex backtracking programs.
Solution
We can implement non-blind backtracking using call/cc to get escape procedures to
various contexts. We store these continuations in a global structure so that they can be
invoked and the process will return to these contexts in the computation.
In [FHK84] the concept of devils,angels and milestones is presented. A devil will
return us to the context in which the last milestone was created. The devil will pass to
the continuation the value that was passed to it. Now this value is used as if it were the
result of the original milestone expression, possibly allowing us to follow a different path.
An angel will send the computation forward to the last encounter with a devil. Again, the
value passed to the angel will be given to the devil’s continuation allowing us to return to
the context of the devil with this value replacing the value returned by the devil. This will
allow us to move to more advanced states. A milestone will record the current context to be
used by any encountered devils.
As a metaphor, we can take the example given in the introduction of this pattern lan-
guage. We begin by reading the first paper and we reach a point where we realize that
20
we need further knowledge. We set a milestone (remembering where we were in this first
paper) and begin reading the references and other material. When we feel that we have
sufficient knowledge to continue with the original paper, we return to that paper (equivalent
to invoking a devil). Possibly, we didn’t read all the references and other related material
before we went back to this original paper. If this is the case, after finishing the original
paper we decide to go back to reading the remaining references and other material. This is
equivalent to invoking an angel.
We also need two datastructures for storing past milestones and future points (where
devils were invoked). A simple approach would be to keep two stacks (past and future)
since in the simplest case we will only be returning to the previous milestone or the previous
invocation of a devil. Other data structures are possible and are often used in artificial
intelligence applications [FHK84].
Sample Code
An implementation of milestone, angels and devils can be seen in [FHK84]. The mile-
stone procedure should simply store the current state in the past entries and return the initial
value.
(define milestone
(lambda (x)
(call/cc (lambda (k)
(begin (push past k)
x)))))
The implementation of a devil will store the current continuation as a future and return to
the last milestone with a new value.
(define devil
(lambda (x)
(call/cc (lambda (k)
(begin (push future k)
((pop past) x))))))
And the angel procedure can be written as:
(define angel
(lambda (x)
((pop future) x)))
And for each of these procedures if the corresponding stack is empty it is set up to return
the identity function (and the angel or devil procedure returns x).
Rationale
We can now use these procedures to store important points in the execution to be re-
turned to, return to stored states to continue computation and return to computations that
21
were only partially complete. With these procedures, we have a simple isolated mechanism
which can be reused in various programs. We can also expand the behaviour by changing
the data structure used by the backtracking mechanism from a stack to another structure.
22
Multitasking
Multitasking allows us to enforce the amount of time that a computation can run. Using
engines and a timer mechanism we allow for processes to be interrupted (and possibly
restarted). Engines use call/cc to get the current context of a computation which is invoking
the engine (so that we may return to it after execution is complete) and to get the current
continuation of a procedure when it is being interrupted so that it can be continued.
Context
It is often beneficial to perform distinct computations in a parallel fashion even if they
are not exactly in parallel. There are also times when we wish to limit the amount of
time a computation can take. An example of these would be the or of several expressions.
Computing the expressions in parallel, if a simple expression quickly evaluates to be true,
the computation of the other expressions can be terminated.
Problem
How do we perform this multitasking? This would include allowing for computations
to run for a limited period of time, interrupted if they do not complete, and later restarted.
Forces
Modifications to the interpreter/compiler or language should be kept to a
minimum.
Using a mechanism such as threads and semaphores may have a high over-
head or may not be available.
Performing the scheduling and state mechanisms in the language will allow
for flexibility but is less efficient than a lower level mechanism.
Solution
We can wrap a procedure in an engine (one can think of an engine as similar to a thread).
The engine implementation will use call/cc to: 1) obtain the continuation of the computation
that invokes the engine so it can be returned to when the engine is complete, and 2) obtain
the continuation of the engine when the timer expires so that it is possible to return to the
computation if called upon [DH89].
The make-engine procedure must take a procedure (of no arguments) which specifies
the computation to be performed by the engine. The engine returned will be a procedure
of three arguments: 1) the amount of time the engine will be allowed to run, 2) a return
procedure which specifies what to do if the engine is done before the time allotted, and 3)
an expire procedure which specifies what to do if the time allotted has run out before the
computation.
We will not discuss the timing mechanism as a method is given in [DH89] for creating
a timer mechanism by overwriting the lambda function or by creating a timed-lambda
function and using it to define all asynchronous functions. We will assume that this timer
23
given in [DH89] is used. With this timer, a tick is consumed by an engine on every function
call.
Example
We can make use of extend-syntax (Scheme’s macro facility), to create a parallel-or which
uses engines as (taken from [DH89]):
(extend-syntax (parallel-or)
((parallel-or e ...)
(first-true (lambda () e) ...)))
(define first-true
(lambda proc-list
(letrec ((engines (queue))
(run (lambda ()
(and (not (empty-queue? engines))
((dequeue engine)
1
(lambda (v t) (or v (run)))
(lambda (e) (enqueue e engines) (run)))))))
(for-each (lambda (proc) (enqueue (make-simple-engine proc) engines))
proc-list)
(run))))
Rationale
With continuations, no modifications are needed to the interpreter or compiler (although
we do need to make use of the macro facility). There is also a small overhead of timer
updates but we can avoid some of these by making some of the code synchronous. We do
not need thread nor semaphores built into the language.
24
3 Summary
We have presented in this pattern language the idea of first class continuations and several
repetitive uses of continuations from non-local exits (with the Escape from a Loop and
Escape from Recursion patterns), to control structures (the Looping with Continuations
and Escape from and Reentry into Recursion patterns) to the more complex behaviours
(Coroutines,Backtracking, and Multitasking patterns). Although continuations are often
confusing and hard to understand (and should normally be avoided for simpler programs),
they present us with the ability to add complex behaviours that would otherwise require
modification to the language, interpretor and/or compiler (provided we have the means to
add to the language).
25
4 Bibliography
References
[DH89] R. K. Dybvig and R. Hieb. Engines from Continuations. Computer
Languages, pages 109–123, 1989.
[Dyb96] R. K. Dybvig. The Scheme Programming Language. Prentice Hall,
1996.
[FHK84] D. P. Friedman, D. T. Haynes, and E. E. Kohlbecker. Programming
with Continuations. In P. Pepper, editor, Program Transformation and
Programming Environments, pages 263–274, 1984.
[HFW86] C. T. Haynes, D. P. Friedman, and M. Wand. Obtaining Coroutines with
Continuations. Computer Languages, pages 143–153, 1986.
[SF89] G. Springer and D. P. Friedman. Scheme and the Art of Programming.
MIT Press and McGraw-Hill, 1989.
[SJ75] G. J. Sussman and G. L. Steele Jr. Scheme: An Interpreter for Extended
Lambda Calculus. MIT AI Memo 349, Massachusetts Institute of Tech-
nology, May 1975.
26
... They are referred to as first-class values in a programming language if they can be passed to functions as parameters, returned from functions as values, or stored in data structures. Languages with first-class continuations can implement a wide variety of features that are often built directly into an interpreter or runtime library [4], [5], [6], [7], [8]. In addition, continuation-passing style (CPS) is a heavily-studied representation strategy sometimes used as an intermediate form in compilers [9]. ...
... Scheme has included first-class continuations since its creation at MIT in the 1970s [11]. Usage and implementation of continuations are common topics in Scheme-related research [7], [12]. ...
... First-class continuations can model features that are often built directly into the interpreter or runtime library of a language that does not expose continuations to the programmer. Such features include coroutines [4], exceptions [5], threads [6], nonblind backtracking [7], and nondeterministic computations [8]. This project concentrates on exceptions and coroutines because they are the most generally applicable. ...
Article
Includes abstract. Thesis (M.S.)--California State University, Chico. Includes bibliographical references (leaves: 77-82).
... Upon entry of each state the itinerary task assigns an alerter function to global variable alerter (lines 4,9,16,21). ...
... Most of the actual work is done in procedure peer-dict (lines [12][13][14][15][16][17][18][19][20][21]. This is a blocking call that takes a key and timeout value as parameters, and returns after timeout seconds with the associated values of all its neighbors. ...
... It allows an arbitrary number of simultaneous outstanding blocking I/O operations, without using more memory than strictly needed to contain application state. We will not discuss the semantics of continuations and the call/cc primitive here; for a thorough description of continuations we refer the reader to [21]. ...
Conference Paper
Full-text available
Besides the currently realized applications, wireless sensor networks can be put to use in logistics processes. However, current WSN software platforms cannot provide the flexibility and safety needed. This paper presents SensorScheme, a runtime environment based on semantics of the Scheme programming language, used to realize a logistics scenario. SensorScheme is a general purpose WSN platform, providing dynamic reprogramming, memory safety (sandboxing), blocking I/O, marshalled communication and compact code transport. We illustrate the use of our platform and provide experimental results that show its speed of operation and energy efficiency.
... In addition to Roundabout, I have also written Envoy, a pattern language for programs that maintain state using closures [11]. Ferguson and Deugo discuss stereotypical ways to build complex control structures using call-with-current-continuation in Scheme [5]. Hanmer describes techniques for writing efficient logic programs in Prolog [7]. ...
Article
Functional programming is a powerful style in which to write programs. However, students and faculty alike often have a hard time appreciating its beauty and learning to exploit its power. These difficulties arise in part because the functional style requires programmers to think at a higher level of abstraction, and in part because functional programs differ in fundamental ways from programs written in more common styles. A pattern language of functional programs can provide programmers with concrete guidance for writing functional programs and at the same time offer a deeper appreciation of functional style. Used effectively, such pattern languages will help functional programming educators reach a broader audience of faculty, students, and professionals. The result will be better programmers and better programs.
... It allows an arbitrary number of simultaneous outstanding blocking I/O operations, without using more memory than strictly needed to contain application state. We will not discuss the semantics of continuations and the call/cc primitive here; for a thorough description of continuations we refer the reader to [6]Table 1: Code sizes of example program ates a continuation, used to return to the function's caller after the timeout. At line 9 a timer is set up to signal the end of the timeout. ...
Conference Paper
Full-text available
Building wireless sensor network applications is a challenging task, and it has become apparent that it is crucial for many sensor networks to be able to load or update the application after deployment. Since communication is a scarce resource and costly in terms of energy, it is important to minimize code size when reprogramming WSNs in the field. This paper introduces SensorScheme as a novel interpreted WSN platform for dynamically loading sensor network applications. It is based on the semantics of the Scheme language and is equipped with high-level programming facilities such as higher-order functions, garbage collection, communication by automatic marshalling of data items, and co-routines to implement blocking I/O operations. SensorScheme makes efficient use of the little available memory in WSN nodes, uses a very compact program notation during wireless transmission, and provides a safe interpreted execution environment, preventing malfunctioning programs from crashing the device. We illustrate the use of SensorScheme and evaluate its code compactness and energy- efficiency.
... It allows an arbitrary number of simultaneous outstanding blocking I/O operations, without using more memory than strictly needed to contain application state. We will not discuss the semantics of continuations and the call/cc primitive here; for a thorough description of continuations we refer the reader to [6]. ...
Conference Paper
Full-text available
The supply chain management business can benefit greatly from automation, as recent developments with RFID technology shows. The use of Wireless Sensor Network technology promises to bring the next leap in efficiency and quality of service. However, current WSN system software does not yet provide the required functionality, flexibility and safety. This paper discusses a scenario showing how WSN technology can benefit supply chain management, and presents SensorScheme, a platform for realizing the scenario. SensorScheme is a general purpose WSN platform, providing a safe execution environment for dynamically loaded programs. It uses high level programming primitives like marshalled communication, automatic memory management, and multiprocessing facilities. SensorScheme makes efficient use of the little available memory present in WSN nodes, to allow larger and more complex programs than the state of the art. We present a SensorScheme implementation and provide experimental results to show its compactness, speed of operation and energy efficiency.
Article
Full-text available
Inspired by ACTORS [7, 17], we have implemented an interpreter for a LISP-like language, SCHEME, based on the lambda calculus [2], but extended for side effects, multiprocessing, and process synchronization. The purpose of this implementation is tutorial. We wish to: 1.alleviate the confusion caused by Micro-PLANNER, CONNIVER, etc., by clarifying the embedding of non-recursive control structures in a recursive host language like LISP. 2.explain how to use these control structures, independent of such issues as pattern matching and data base manipulation. 3.have a simple concrete experimental domain for certain issues of programming semantics and style. This paper is organized into sections. The first section is a short “reference manual” containing specifications for all the unusual features of SCHEME. Next, we present a sequence of programming examples which illustrate various programming styles, and how to use them. This will raise certain issues of semantics which we will try to clarify with lambda calculus in the third section. In the fourth section we will give a general discussion of the issues facing an implementor of an interpreter for a language based on lambda calculus. Finally, we will present a completely annotated interpreter for SCHEME, written in MacLISP [13], to acquaint programmers with the tricks of the trade of implementing non-recursive control structures in a recursive language like LISP. This report describes research done at the Artificial Intelligence Laboratory of the Massachusetts Institute of Technology. Support for the laboratory's artificial intelligence research is provided in part by the Advanced Research Projects Agency of the Department of Defense under Office of Naval Research contract N00014-75-C-0643.
Article
Progress in programming language design has often been achieved by making an abstraction a “first class object”, one that can be passed to and returned from procedures and entered in data structures. For example, the importance of functional parameters has long been recognized, though it is only more recently that actor semantics [2] and object oriented programming have demonstrated the power of first class functional objects. This paper illustrates, with a novel example, the power of first class control objects, called continuations.
Article
Continuations, when made available to the programmer as first class objects, provide a general control abstraction for sequential computation. The power of first class continuations is demonstrated by implementing a variety of coroutine mechanisms using only continuations and functional abstraction. The importance of general abstraction mechanisms such as continuations is discussed.
Article
Engines provide the means for a computation to be run for a limited period of time, interrupted if it does not complete in that time, and later restarted from the point of interruption. Previous work on engines demonstrated that engines can be implemented by defining a new interpreter to support them. This article demonstrates that engines may be defined in terms of continuations and timer interrupts and thereby incorporated into an existing language implementation. The article extends the engine mechanisms to solve several problems with nestable engines, and demonstrates that the extended mechanism can be implemented in terms of continuations as well.