When we invoke any of the procedures that we have discussed so far in the
course, we get back a single result. However, there are many computations
that we can describe most naturally as having two or more results. A
typical example is the division of integers, as taught in elementary
schools: When you divide 647 by 7, you get a quotient of 92 and a remainder
of 3. It is not obvious that one of these results is more important or
more significant than the other, and in fact Scheme provides a primitive
for each one: quotient for the quotient,
remainder for the remainder:
> (quotient 647 7) 92 > (remainder 647 7) 3
But since the same underlying computation is carried out in either case, it
would make more sense to have a single procedure divide that
would, when invoked, produce both results:
> (divide 647 7) 92 3
The procedure call `(divide 647 7)' is an example of a
multiple-valued expression in Scheme. Evaluating a multiple-valued
expression gives you several results, as the interaction displayed above
shows.
Other kinds of expressions can also have multiple values:
if-expression is multiple-valued if the selected
branch, the consequent or the alternate, is multiple-valued.cond-expression is multiple-valued if the last
subexpression in the selected cond-clause is
multiple-valued.let-, let*-, letrec-, or named
let-expression is multiple-valued if its body is
multiple-valued.
However, identifiers, constants, lambda-expressions,
and-expressions, and or-expressions are never
multiple-valued.
Of course, we have to be careful about where we write multiple-valued
expressions. We can't put one in the test position of an
if-expression, for instance. If the test expression could be
multiple-valued, it might have both #t and #f
among its values! No, the test has to produce exactly one value, so that
we know unambiguously whether to evaluate the consequent or the alternate.
Similarly, we can't write a multiple-valued expression as a subexpression of a procedure call, since the procedure to be called has to be some one particular procedure, and each of the arguments that we supply to it has to be some one particular value.
However, we can write a multiple-valued expression as the body of
a lambda-expression, thus constructing our own multiple-result
procedures, and this is the context in which you can expect to see them
most frequently.
values procedure
All multiple-valued expressions enter Scheme, directly or indirectly,
through calls to the primitive procedure values.
Values is a variable-arity procedure that returns its
arguments without change -- all of them.
> (values 650 682 513 861) 650 682 513 861
When the values procedure is given only one argument, it returns
that argument without change:
> (values #\a) #\a
It is also possible to call values with no arguments, in which
case, of course, it returns no values:
> (values)
This is not an error or a non-terminating computation, but simply a computation that produces nothing when it finishes, like a committee that decides not to issue a report (which is sometimes the most useful thing a committee can do).
As a quick example, let's define a procedure
mixed-number-parts that takes a rational number as its
argument and returns its integer part and its proper fractional part:
(define mixed-number-parts
(lambda (num)
(let ((integer-part (truncate num)))
(values integer-part (- num integer-part)))))
What are the results of applying mixed-number-parts to the
rational number 17/5? Why? What happens when you apply it to 4/7? To
38/19?
Define and test a procedure tallies-by-parity that takes any
list of integers as its argument and returns two values, the number of even
integers in the list and the number of odd integers in the list. (Hint:
Use tail recursion.)
call-with-values procedureSometimes we want to use the values of a multiple-valued expression in some further computation instead of returning them directly. The fact that a multiple-valued expression cannot be used as an argument in a procedure call makes it difficult to do this.
Scheme's solution is another primitive procedure,
call-with-values, that manages the flow of data from a
multiple-valued expression into a larger computation.
Call-with-values is a higher-order procedure that takes two
arguments, a producer procedure that generates multiple values and a
consumer procedure that accepts them.
The producer procedure takes no arguments and has a multiple-valued
expression as its body. Call-with-values invokes the producer
and collects all of the values that it returns.
Call-with-values then invokes the consumer, providing the
collected values as arguments. The arity of the consumer must therefore
be compatible with the number of values delivered by the producer.
Call-with-values returns whatever the consumer returns.
Suppose, for instance, that we want to recover the integer part and the
proper fractional part of 1173/83 and then subtract the fractional part
from the integer part. Here's how we could invoke
call-with-values to perform the computation:
> (call-with-values (lambda () (mixed-number-parts 1173/83)) -) 1151/83
The producer in this case is (lambda () (mixed-number-parts
1173/83), which returns two values when invoked. The consumer is
-, which accepts two values and returns their difference.
As a more practical example, let's define a procedure that takes two
arguments, a predicate pred and a list ls, and
returns two lists, one consisting of all of the elements of ls
that satisfy pred, the other consisting of all of the elements
of ls that do not satisfy pred.
Our basic strategy is list recursion. In the base case, where
ls is the empty list, we want to return two lists, both empty.
That's easy -- we'll just write (values null null). In any
other case, we divide ls into its car and its cdr and invoke
the procedure recursively to deal with the cdr. The recursive call will
return two lists: the list of elements of the cdr that satisfy
pred, and the list of elements of the cdr that do not. We
cons the car of the list onto one or the other of these recursive results,
depending on whether it does or does not satisfy pred, and
return both the result of the cons and the other recursive result.
Translating into Scheme:
(define partition
(lambda (pred ls)
(letrec ((recurrer
(lambda (ls)
(if (null? ls)
(values null null)
(call-with-values
(lambda () (recurrer (cdr ls)))
(lambda (ins outs)
(if (pred (car ls))
(values (cons (car ls) ins) outs)
(values ins (cons (car ls) outs)))))))))
(recurrer ls))))
Notice how one uses call-with-values to manage the transfer of
two values from the multiple-valued expression (recurrer (cdr
ls)) into the part of the computation that consumes those values.
Think of the body of the producer, the expression `(recurrer (cdr
ls))', as ready to supply its results when asked. Think of the body
of the consumer, the inner if-expression, as ready to receive
those results and operate on them to construct the final values that
recurrer will return. The role of
call-with-values is to activate and mediate these two packaged
components of the overall computation.
Define and test a recursive procedure that takes an association list
als as argument and returns two results: a list of the keys of
als, and a list of the values of als.
Define a procedure explode that takes a list as its argument
and returns all of the elements of that list (separately) as values.
> (explode (list 'a 'b 'c 'd)) a b c d > (explode null) ; This expression has no values!
(Hint: Use apply.)
Our textbook (p. 201) defines a higher-order procedure named
compose that takes two procedures of arity 1 and invokes them
in succession, applying the first to the result returned by the second:
(define compose
(lambda (f g)
(lambda (x)
(f (g x)))))
For instance, the cadr procedure could be computed as
(compose car cdr) -- that is, ``take the car of the cdr'' --
and string-length as (compose length
string->list).
With the help of variable-arity lambda-expressions and
call-with-values, we can write a generalized
compose procedure that can be applied to procedures of any
arity, provided that the first one accepts as many values as the second
produces:
(define compose
(lambda (outer inner)
(lambda arguments
(call-with-values (lambda () (apply inner arguments))
outer))))
For example, (compose append partition) takes two arguments, a
predicate and a list, and returns a list in which the elements of the given
list have been rearranged, in such a way that the elements that satisfy the
predicate precede those that do not. The partition procedure
is applied first to the two arguments and returns two separate lists; the
append procedure pastes the two lists together again to
produce the rearranged list.
Describe the procedure that is the value of the expression `(compose
string-append explode)'.
Using compose, define and test a predicate that takes a
predicate pred and a list ls as arguments and
returns #t if ls contains more elements that
satisfy pred than elements that do not, #f
otherwise.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/multiple-valued-procedures.xhtml
created March 17, 2000
last revised April 3, 2000