

In order to discuss the true potential of callcc it will
be useful to discuss it's use in scheme, rather than ML. It is in
scheme that it has had it's greatest application. The truth is that it
is not as easy to make use of in a strongly typed language like
ML.
To understand the limitation, let's look at a couple of simple examples:
open SMLofNJ.Cont; val x = callcc (fn k => 3); val y = callcc (fn k => (2 * (throw k 3)));
What do these do?
So, inside the function that callcc calls, we can use the
continuation, or not. In either case, the expression on the right
hand side of the assignment must have the same type; in this case,
int. So, the type of callcc is:
val it =: ('a cont -> 'a) -> 'a
Unfortunately, many of the cool tricks with callcc
involve capturing the continuation in ways that would violate this
restriction, such as:
val k' = callcc (fn k => k);
It's probably just as well that this won't work. After this assignment, what would it mean to say:
To handle this properly, we would have to account for all the subsequent user interactions in the "future" of the value on the right of the original assignment toval z = throw k' 4;
k'. Scheme, which does
allow fo this, fudges this by only considering the "future" to include
up to the next call to the read-eval-print loop.
If callcc is allowed to return its own continuation as
its value (without calling it) things can get pretty wierd. Consider
the following two scheme expressions (In scheme you do not throw a
continuation, you just apply it.):
(let ([x (call/cc (lambda (k) k))])
(x (lambda (y) "hello")))
(((call/cc (lambda (k) k)) (lambda (x) x)) "hello")
The latter probably has the most confusing control flow of any expression of its size in any language!
Now, consider the following scheme function:
(define reentry_point 0)
(define (factorial n)
(if (= n 0)
(call/cc (lambda (k) (sequence
(set! reentry_point k)
1)))
(* n (factorial (- n 1)))))
What happens when we call (factorial 4)? What is
reentry_point bound to? What happens when we call
(reentry_point 10)?
This idea can be moved over to ML, but it is a bit tricky. First there is a type issue. We need to create a reference variable to store the continuation. But what is it's initial value to be? In scheme we just put 0, but this won't work in ML since it must be a value of the same type as we will eventually want to put there. But there is no generic integer continuation value available to us. The solution is to use an option type.
Once this is resolved there are no other type problems. In this case
the call to call_cc is well typed. So, we can write:
val reentry_point = ref (NONE : int cont option);
fun fact 0 = callcc (fn k =>
(
reentry_point := SOME k ;
1
))
| fact n = n * fact (n-1);
But there is another problem: SML-NJ is pickier about what constitutes
the "future" of a value. It will refuse to call any continuation that
would require rewriting the history of some top-level user
interaction. Thus if we try to extract the continuation from
reentry_point and throw it, we will have a
problem:
- val (SOME r) = !reentry_point; val r = cont : int cont - throw r 1; uncaught exception Top_level_callcc
We can, however, make this idea work if no user interaction is involved. Here is some code that relies on the re-enterable factorial:
fun reenter 5 = ()
| reenter n = case !reentry_point
of NONE => ()
| SOME k => throw k n;
val multiplier = ref 1;
fun multi_fact n =
(
print "Initial call to factorial: ";
print (Int.toString (fact n)); print "\n" ;
multiplier := !multiplier + 1 ;
reenter (!multiplier)
);
This idea of re-entering a procedure can go quite far. Here is ML code for a cooperative multi-tasking implementation of a simple producer/consumer system. The producer puts a value into an integer refernce and the consumer reads it. Each time either one completes an action, it stops and re-enters the other one. As part of the re-entry it passes to the other the re-entry continuation that the other should call to allow this one to restart.
datatype state = S of state cont;
fun resume (S k) = callcc (fn k' => throw k (S k'));
val buffer = ref 0;
fun produce n consumer_state =
(
print "producing " ;
print (Int.toString n) ;
print "\n";
buffer := n ;
produce (n+1) (resume consumer_state)
)
fun consume producer_state =
(
print "consuming " ;
print (Int.toString (!buffer)) ;
print "\n\n";
consume (resume producer_state)
)
fun init_producer n = callcc (fn k => produce n (S k));
fun run () = consume (init_producer 0);

|
|
This page copyright ©1999 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Monday, March 22, 1999 at 1:00 PM. |
http://cs.hmc.edu/~hodas/courses/cs131/lectures/lecture15.html | |