gls@THINK.COM (08/02/88)
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