5.7. Multiple Values


5.7. Multiple Values

While all Scheme primitives and most user-defined procedures return exactly one value, some programming problems are best solved by returning something other than one value. For example, a procedure that partitions a list of values into two sublists needs to return two values. While it is possible for the producer of multiple values to package them into a data structure and for the consumer to extract them, it is often cleaner to use the built in multiple-values interface. This interface consists of two procedures: values and call-with-values. The former produces multiple values and the latter links procedures that produce multiple-value values with procedures that consume them. The multiple values interface appears in the Revised5 Report but not in the ANSI/IEEE standard.

(values obj ...)

procedure

returns: see discussion following

The procedure values accepts any number of arguments and simply passes (returns) the arguments to its continuation.

 (values)  (values 1)  1 (values 1 2 3)  1                 2                 3 (define head&tail   (lambda (ls)     (values (car ls) (cdr ls)))) (head&tail '(a b c))  a                               (b c) 

(call-with-values producer consumer)

procedure

returns: see discussion following

producer and consumer must be procedures. call-with-values applies consumer to the values returned by invoking producer without arguments.

 (call-with-values   (lambda () (values 'bond 'james))   (lambda (x y) (cons y x)))  (james . bond) (call-with-values values list)  '() 

In the second example, values itself serves as the producer. It receives no arguments and thus returns no values. list is thus applied to no arguments and so returns the empty list.

The procedure dxdy defined below computes the change in x and y coordinates for a pair of points whose coordinates are represented by (x . y) pairs.

 (define dxdy   (lambda (p1 p2)     (values (- (car p2) (car p1))             (- (cdr p2) (cdr p1))))) (dxdy '(0 . 0) '(0 . 5))  0                            5 

dxdy can be used to compute the length and slope of a segment represented by two endpoints.

 (define segment-length   (lambda (p1 p2)     (call-with-values       (lambda () (dxdy p1 p2))       (lambda (dx dy) (sqrt (+ (* dx dx) (* dy dy))))))) (define segment-slope   (lambda (p1 p2)     (call-with-values       (lambda () (dxdy p1 p2))       (lambda (dx dy) (/ dy dx))))) (segment-length '(1 . 4) '(4 . 8))  5 (segment-slope '(1 . 4) '(4 . 8))  4/3 

We can of course combine these to form one procedure that returns two values.

 (define describe-segment   (lambda (p1 p2)     (call-with-values       (lambda () (dxdy p1 p2))       (lambda (dx dy)         (values           (sqrt (+ (* dx dx) (* dy dy)))           (/ dy dx)))))) (describe-segment '(1 . 4) '(4 . 8))  5                                       4/3 

The example below employs multiple values to divide a list nondestructively into two sublists of alternating elements.

 (define split   (lambda (ls)     (if (or (null? ls) (null? (cdr ls)))         (values ls '())         (call-with-values           (lambda () (split (cddr ls)))           (lambda (odds evens)             (values (cons (car ls) odds)                     (cons (cadr ls) evens))))))) (split '(a b c d e f))  (a c e)                          (b d f) 

At each level of recursion, the procedure split returns two values: a list of the odd-numbered elements from the argument list and a list of the even-numbered elements.

The continuation of a call to values need not be one established by a call to call-with-values, nor must only values be used to return to a continuation established by call-with-values. In particular, (values v) and v are equivalent in all situations. For example:

 (+ (values 2) 4)  6 (if (values #t) 1 2)  1 (call-with-values   (lambda () 4)   (lambda (x) x))  4 

Similarly, values may be used to pass any number of values to a continuation that ignores the values, as in:

 (begin (values 1 2 3) 4)  4 

Because a continuation may accept zero or more than one value, continuations obtained via call-with-current-continuation (call/cc) may accept zero or more than one argument.

 (call-with-values   (lambda ()     (call/cc (lambda (k) (k 2 3))))   (lambda (x y) (list x y)))  (2 3) 

Many Scheme operators pass along multiple values. Most of these are "automatic," in the sense that nothing special must be done by the implementation to make this happen. The usual expansion of let into a direct lambda call automatically propagates multiple values produced by the body of the let. Other operators must be coded specially to pass along multiple values. For example, if the computation delayed by delay produces multiple values, all of the values must be retained so that force can return them. This is easily accomplished via call-with-values, apply, and values, as the following alternative definition of make-promise (see Section 5.6) demonstrates.

 (define make-promise   (lambda (p)     (let ((vals #f) (set? #f))       (lambda ()         (if (not set?)             (call-with-values p               (lambda x                 (if (not set?)                     (begin (set! vals x)                            (set! set? #t))))))         (apply values vals))))) (define p (delay (values 1 2 3))) (force p)  1             2             3 (call-with-values (lambda () (force p)) +)  6 

Other operators that must be coded similarly to pass along multiple return values include call-with-input-file, call-with-output-file, with-input-from-file, with-output-to-file, and dynamic-wind.

The behavior is unspecified when a continuation expecting exactly one value receives zero values or more than one value. For example, the behavior of each of the following expressions is unspecified.

 (if (values 1 2) 'x 'y) (+ (values) 5) 

Similarly, since there is no requirement to signal an error when the wrong number of arguments is passed to a procedure (although most implementations do so), the behavior of each of the following expressions is also unspecified.

 (call-with-values   (lambda () (values 2 3 4))   (lambda (x y) x)) (call-with-values   (lambda () (call/cc (lambda (k) (k 0))))   (lambda (x y) x)) 

In the interests of catching possible coding errors and for consistency with the signaling of errors when procedures receive incorrect numbers of arguments, some implementations, including Chez Scheme, signal an error whenever an unexpected number of values is received. This includes the case where too few or too many are passed to the consumer of a call-with-values call and the case where zero or more than one value is passed to a single-value continuation, such as in the test part of an if expression. An implementation may, however, silently suppress additional values or supply defaults for missing values.

Programs that wish to force extra values to be ignored in particular contexts can do so easily by calling call-with-values explicitly. A syntactic form, which we might call first, can be defined to abstract the discarding of more than one value when only one is desired.

 (define-syntax first   (syntax-rules ()     (( expr)      (call-with-values        (lambda () expr)        (lambda (x . y) x))))) (if (first (values #t #f)) 'a 'b)  a 

Since producer is most often a lambda expression, it is often convenient to use a syntactic extension that suppresses the lambda expression in the interest of readability.

 (define-syntax with-values   (syntax-rules ()     (( expr consumer)      (call-with-values (lambda () expr) consumer)))) (with-values (values 1 2) list)  (1 2) (with-values (split '(1 2 3 4))   (lambda (odds evens) evens))      (2 4) 

If the consumer is also a lambda expression, the multiple-value variant of let defined below might be even more convenient.

 (define-syntax let-values   (syntax-rules ()     ((_ ((fmls e0)) e1 e2 ...)      (with-values e0        (lambda fmls e1 e2 ...))))) (let-values (((odds evens) (split '(1 2 3 4))))   evens)  (2 4) (let-values ((ls (values 'a 'b 'c)))   ls)  (a b c) 

This version of let-values is restricted to binding one set of variables to the values produced by one expression. A more general implementation of let-values that binds more than one set of variables to corresponding sets of values is given on page 200.

The definitions of values and call-with-values (and concomitant redefinition of call/cc) below demonstrate that the multiple return values interface can be implemented entirely in Scheme. No error checking can be done, however, for the case in which more than one value is returned to a single-value context such as the test part of an if expression.

 (define call/cc call/cc) (define values #f) (define call-with-values #f) (let ((magic (cons 'multiple 'values)))   (define magic?     (lambda (x)       (and (pair? x) (eq? (car x) magic))))   (set! call/cc     (let ((primitive-call/cc call/cc))       (lambda (p)         (primitive-call/cc           (lambda (k)             (p (lambda args                   (k (apply values args)))))))))   (set! values     (lambda args       (if (and (not (null? args)) (null? (cdr args)))           (car args)           (cons magic args))))   (set! call-with-values     (lambda (producer consumer)       (let ((x (producer)))         (if (magic? x)             (apply consumer (cdr x))             (consumer x)))))) 

Multiple values can be implemented much more efficiently [1], but this code serves to illustrate the meanings of the operators and can be used to provide multiple values in implementations that do not support them.




The Scheme Programming Language
The Scheme Programming Language
ISBN: 026251298X
EAN: 2147483647
Year: 2003
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net