[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Continuations and multiple values



Here I write down some of the ideas I put forward on multiple values
at the Scheme meeting of Sunday, July 24, 1988.

Different Lisp dialects have different theories about how to handle
multiple values when the "wrong" number are returned.  The main point
of this note is that the implementation of these theories can be
moved out of CWCC and into VALUES, which can be written as user-level
code provided that some simple predicates are provided.

Let us suppose that every continuation accepts noly the "correct"
number of values.  In other words, the continuation for evaluating
a subform of a combination requires one value; the continuation
for evaluating an IF predicate requires one value; and the continuation
for evaluating a non-final subform of BEGIN requires zero values.
Define (WITH-VALUES thunk f) to call the thunk with a continuation
that accepts as many arguments as f does.  (In effect the continuation
is the composition of the continuation of the WITH-VALUES form with f.)

Then the simplest definition of VALUES is:

(define (values . r) (cwcc (lambda (k) (apply k r))))

Let the predicate (ACCEPTS? f n) be true if f will accept n arguments,
and false otherwise.  (This is generally useful for writing interpreters.)

Suppose we want to allow excess values to be ignored.  We can then
write:

(define (values . r)
  (define (ignore-excess-values k z)
    (if (accepts? k (length z))
	(apply k z)
	(if (null z)
	    (apply k r)      ;take the error with all values
	    (ignore-excess-values k (cdr z)))))
  (cwcc (lambda (k) (ignore-excess-values k r))))

A similar technique can be used to provide extra #F values as well if not
enough values were provided.  (We don't know when to stop, but every
continuation must accept some number of values, so the process must
terminate.)

(define (values . r)
  (define (ignore-excess-values k z)
    (if (accepts? k (length z))
	(apply k z)
	(if (null z)
	    (supply-default-falses k r)
	    (ignore-excess-values k (cdr z)))))
  (define (supply-default-falses k z)
    (if (accepts? k (length z))
	(apply k z)
	(supply-default-falses k (cons '#f z))))
  (cwcc (lambda (k) (ignore-excess-values k r))))

Now the assumption that a BEGIN continuation requires exactly zero values
is a bit stringent.  This may be a good thing; perhaps one ought to mark
explicitly where a value is being discarded.  One might have the syntax

(void x)  ->  (with-values (lambda () x) (lambda r (values)))

and then write

(begin (set! x 3)
       (void (valued-function-with-side-effect x))
       (+ x 1))

where we assume that  set!  is already defined to return zero values.
If this arrangement is not acceptable then perhaps BEGIN continuations
should accept 0 or 1 value, discarding the value if any.

Another paradigm we might wish to emulate is that where VALUES may
not be used except with continuations explicitly created by WITH-VALUES.
Here are two ways to do that; one requires a new predicate, and the
other is a crock.

If we have a predicate (WITH-VALUES-CONTINUATION? f) that is true
precisely of continuations created by WITH-VALUES, then we write simply

(define (values . r)
  (cwcc (lambda (k)
	  (if (with-values-continuation? k)
	      ...
	      (error)))))

On the other hand, instead of requiring such a built-in predicate
we can take advantage of the fact that no other continuation takes
more than one value (this is the crock):

(define old-with-values with-values)

(define (with-values thunk f)
  (old-with-values thunk (lambda (foo bar . r) (apply f r))))

(define (values . r)
  (cwcc (lambda (k)
          (if (accepts? k 2) (apply k (cons #f (cons #f r))) (error)))))

The "foo bar" arguments ensure that only a VALUES can correctly provide the
values to a WITH-VALUES.  The ACCEPTS? test determines whether the
continuation K was actually provided by a WITH-VALUES.

--Guy Steele