At its core, Scheme's evaluation semantics is multiple-value based. Continuations can accept an arbitrary number of values and expressions can yield an arbitrary number of values. This is in contrast to the functional languages ML and Haskell." (Marc Nieper-Wißkirchen, SRFI 210)
History:
-
R5RS introduced multiple-value returns, supported with the
values
andcall-with-values
syntax. -
R6RS added the
let-values
andlet*-values
forms, previously defined in SRFI 11. -
R7RS has added
define-values
.
R7RS-small contains the following five constructs dealing with multiple values:
-
call-with-values
- used to pass values returned from a producer procedure to a consumer -
define-values
- used likedefine
, but binds values to identifiers -
let-values
- used likelet
, but binds values to identifiers -
let*-values
- used likelet*
, but binds values to identifiers with an environment covering the previous bindings -
values
- bundles its arguments together as a set of values
What are "multiple values"? As the name suggests, they are representations of
more than one value - but they are bound together. This binding is not the
same as some kind of data structure, like a list
. Notice the following:
gosh$ (values 1 2) <1> 1 2
- This creates multiple values from the values 1 and 2.
As a more useful example, (scheme base)
contains the procedure
exact-integer-sqrt
. The definition for this states: "Returns two non-negative
exact integers s and r".
gosh$ (exact-integer-sqrt 16) 4 0 gosh$ (exact-integer-sqrt 17) 4 1
How can we get and use all of the returned values?
This is where the define-
and let-
forms come in.
define-values
works like define
but matches up a series of identifiers
with the respective returned value:
gosh$ (define-values (root remainder) (exact-integer-sqrt 17)) remainder gosh$ root 4 gosh$ remainder 1
As define-values
is matching a list of formals (like lambda
), it is easy to
get values as lists, etc:
gosh$ (define-values vals (values 1 2 3)) vals gosh$ vals (1 2 3) gosh$ (list? vals) #t gosh$ (define-values (head . rest) (values 1 2 3)) rest gosh$ head 1 gosh$ rest (2 3)
There are no surprises with let-values
and let*-values
: these work
in the same way as let
and let*
, but use multiple identifiers to
match multiple values:
(define (find-roots x y) (let-values (((x-root x-remainder) (exact-integer-sqrt x)) ; <1> ((y-root y-remainder) (exact-integer-sqrt y))) (list x-root y-root))) ; <2>
- Collect and name the multiple values
- Return some in a list
And with let*-values
:
(define (roots+total-remainder x y) (let*-values (((x-root x-remainder) (exact-integer-sqrt x)) ((y-root y-remainder) (exact-integer-sqrt y)) ((roots total-remainder) (values (list x-root y-root) ; <1> (+ x-remainder y-remainder)))) (list roots total-remainder)))
-
It's a
let*-
form, so we can access the previous definitions.
The remaining form, call-with-values
, ties together the production and
consumption of multiple values: a producer is a zero-argument procedure that
returns multiple values, using a values
expression, and a consumer is a
procedure that accepts these values as its arguments.
Notice the little "gotcha" that the producer must be a zero-argument procedure.
(define (show-root n) (call-with-values (lambda () (exact-integer-sqrt n)) ; <1> (lambda (s r) (display (square s)) ; <2> (display " + ") (display r) (newline)))) gosh$ (show-root 15) 9 + 6
- The producer procedure, returns two values.
- The consumer procedure accepts those two values as its arguments.
Two SRFIs, SRFI 8 and SRFI 210, are particularly targetted at multiple values: these have both been accepted into the Yellow Edition of R7RS-large.
This SRFI appears in several other SRFIs, its single definition often copy-pasted into the reference implementation of other libraries.
The SRFI provides the receive
syntax, to improve on call-with-values
.
Specifically, instead of having a zero-argument procedure as producer,
receive
accepts any expression as a producer, and its values are bound
into a set of provided identifiers. These can then be processed within
the expression's body.
The show-root
example above can be rewritten to use receive
as:
(define (show-root n) (receive (s r) (exact-integer-sqrt n) ; <1> (display (square s)) ; <2> (display " + ") (display r) (newline)))
- The multiple-return values are captured into the given identifiers.
- The body can work with those identifiers.
Of course, in a post-R5RS world, we have let-values
as an alternative,
which also has the advantage of being able to "receive" values from
multiple producers. The above example using let-values
:
(define (show-root n) (let-values (((s r) (exact-integer-sqrt n))) ; <1> (display (square s)) ; <2> (display " + ") (display r) (newline)))
- The multiple-return values are captured into the given identifiers.
- The body can work with those identifiers.
Aimed at introducing procedures and syntax for dealing with multiple values, such as creating lists and vectors from expressions returning multiple values and procedures returning the elements of a list or vector as multiple values.
This SRFI introduces a number of syntactic forms and procedures. I have divided these into separate groups, with related functionality, but not explained every construct.
- Definition
(coarity producer)
- Description
-
producer
- an expression producing multiple values
Evaluates producer
and returns the number of resulting values.
- Example
gosh[r7rs.user]$ (coarity 3) 1 gosh[r7rs.user]$ (coarity (values 1 2 3)) 3 gosh[r7rs.user]$ (coarity (exact-integer-sqrt 11)) 2
- Definition
(set!-values formals producer)
- Description
-
formals
- a formal arguments list -
producer
- an expression producing multiple values
Evaluates producer
and assigns its multiple values to identifiers
in the formal arguments list. It has the same relation to set!
as
define-values
does to define
.
- Example
gosh[r7rs.user]$ (define x 2) x gosh[r7rs.user]$ (define y 3) y gosh[r7rs.user]$ (set!-values (x y) (exact-integer-sqrt 11)) 2 gosh[r7rs.user]$ x 3 gosh[r7rs.user]$ y 2
- Definition
(value index value-1 ...) + (value/mv index value-1 ... [producer])
- Description
-
index
- a positive integer -
value-1
- an expression -
producer
- an optional expression producing multiple values
Evaluates one or more values and returns the index
position value.
For value/mv
, the last expression can produce multiple values.
- Error
If index
is out of range, or a multiple-values producer is in any but the
last position.
- Example
gosh[r7rs.user]$ (value 1 'a 'b 'c) <1> b gosh[r7rs.user]$ (value/mv 1 'a 'b 'c) b gosh[r7rs.user]$ (value/mv 2 'a 'b (values 'c 'd)) <2> c gosh[r7rs.user]$ (value 2 'a 'b (values 'c 'd)) c gosh[r7rs.user]$ (value 3 'a 'b (values 'c 'd)) <3> *** ERROR: argument out of range: 3 gosh[r7rs.user]$ (value/mv 3 'a 'b (values 'c 'd)) d gosh[r7rs.user]$ (value/mv 1 'a (values 'b 'c) 'd) <4> *** ERROR: received more values than expected
- Both work with single value expressions to return the indexed value.
-
Using
values
can work like a single value. -
But the
value/mv
form can use the values as part of the indexed range. -
For
value/mv
, any producer must be in the last position.
- Definition
(case-receive producer clause-1 ...)
- Description
-
producer
- an expression producing multiple values -
clause-1
- each clause is of form (formals body)
A matching clause is found, matching formals
in each clause against
the values returned by the producer, as done in lambda
. And then
the body
of that matching clause is evaluated, in the context of
any bindings introduced by the match.
- Example
We could adapt exact-integer-sqrt
so it returns a single value if the
remainder is 0:
(define (new-eis n) (let-values (((s r) (exact-integer-sqrt n))) (if (zero? r) s (values s r))))
And then write a function to choose how to display the number:
(define (display-root n) (case-receive (new-eis n) ((s) (display "Exact root: ") (display s) (newline)) ((s r) (display "Inexact, with remainder: ") (display r) (newline)))) gosh[r7rs.user]$ (display-root 3) Inexact, with remainder: 2 gosh[r7rs.user]$ (display-root 4) Exact root: 2
The identity
procedure is equivalent to values
.
- Definition
(list/mv value-1 ... [producer])
- Description
-
value-1
- an expression -
producer
- an optional expression producing multiple values
Evaluates one or more values and producer, returning all the values as a list.
- Error
If a multiple-values producer is in any but the last position.
- Example
gosh[r7rs.user]$ (list/mv (values 'a 'b 'c)) (a b c) gosh[r7rs.user]$ (list/mv 'a 'b 'c) (a b c) gosh[r7rs.user]$ (list/mv 'a 'b 'c (values 'd 'e)) (a b c d e) gosh[r7rs.user]$ (list/mv 'a 'b 'c (values 'd 'e) 'f) *** ERROR: received more values than expected
There are also natural extensions for vector and (srfi 195) box types, as
vector/mv
and box/mv
.
- Definition
(list-values list-items)
- Description
-
list-items
- a list of values
Returns the given list of values as multiple values.
- Error
If list-items
is not a list.
There are also natural extensions for vector and (srfi 195) box types, as
vector-values
and box-values
.
- Definition
(apply/mv procedure value-1 ... [producer])
- Description
-
procedure
- a positive integer -
value-1
- an expression -
producer
- an optional expression producing multiple values
Evaluates one or more values and an optional producer, and then applies the given procedure to the values.
- Error
If a multiple-values producer is in any but the last position.
- Example
gosh[r7rs.user]$ (apply/mv string #\a #\b) "ab" gosh[r7rs.user]$ (apply/mv string #\a #\b (values #\c #\d)) "abcd" gosh[r7rs.user]$ (string #\a #\b #\c #\d) <1> "abcd"
- The previous example is the same as this procedure application.
- Definition
(call/mv consumer producer-1 ...)
- Description
-
consumer
- a procedure that will be called on the values returned from the producers -
producer-1
- one or more expressions producing multiple values
Evaluates the procedures, collecting their multiple values together, and then applies the consumer on the values.
- Example
We can use this to convert multiple values into a list or vector:
gosh[r7rs.user]$ (call/mv list (exact-integer-sqrt 26)) (5 1) gosh[r7rs.user]$ (call/mv vector (exact-integer-sqrt 26) (exact-integer-sqrt 3)) #(5 1 1 2)
- Definition
(map-values procedure)
- Description
-
procedure
- a procedure accepting and returning a single value
Returns a procedure which accepts an arbitrary number of arguments. When
applied, it applies the given procedure
to each of the arguments, returning
the results as multiple values.
- Example
gosh[r7rs.user]$ ((map-values exact-integer-sqrt) 3 26) 1 5
- Definition
(with-values producer consumer)
- Description
-
producer
- an expression which returns multiple values -
consumer
- a procedure accepting the values from the producer
The producer
and consumer
are evaluated, and the procedure resulting
from evaluating the consumer is applied to the values results from
evaluating the producer.
This is like call-with-values
except the producer is simply evaluated, not
called.
- Example
Rewriting our earlier show-root
procedure shows the difference:
(define (show-root n) (with-values (exact-integer-sqrt n) ; <1> (lambda (s r) (display (square s)) ; <2> (display " + ") (display r) (newline)))) gosh$ (show-root 15) 9 + 6
- The producer returns two values.
- The consumer procedure accepts those two values as its arguments.
- Definition
(bind/mv producer procedure-1 ...)
- Description
-
producer
- an expression producing multiple values -
procedure-1
- each procedure accepts the values from the preceding procedure, and returns multiple values
Chains a series of procedures together, passing multiple values between them.
- Example
bind/mv
can be used as a simple form of procedure composition, where multiple
values returned from one part are passed on to the next procedure in the line:
gosh[r7rs.user]$ (define (display-pair s r) (display s) (display " + ") (display r) (newline)) <1> display-pair gosh[r7rs.user]$ (bind/mv 13 exact-integer-sqrt display-pair) 3 + 4
- Define a procedure which takes two values and displays them.
There are also forms specialised for producers of different datatypes:
-
bind/list
- uses a list as the produced values -
bind/box
- likebind/list
but for a (srfi 195) box. -
bind
- likebind/list
but for any object.
- Definition
(compose-left procedure-1 ...)
- Description
-
procedure-1
- the procedures should accept an appropriate number
of arguments and return multiple values so the chain is well formed.
Constructs a left-composition of its arguments, so that:
((compose-left f g h) args) #| is equivalent to |# (apply/mv h (apply/mv g (apply f args)))
- Examples
compose-left
is not much different to normal function composition.
For example, we could compose the odd?
and not
functions together
to make a not-odd
function:
gosh[r7rs.user]$ (define not-odd (compose-left odd? not)) not-odd gosh[r7rs.user]$ (not-odd 3) #f gosh[r7rs.user]$ (not-odd 4) #t
The difference is that compose-left
internally uses apply/mv
to apply the
second procedure to the first: hence the first procedure can return multiple
values, which the second one can then use. The following example composes the
multiple-value returning exact-integer-sqrt
with a procedure to display the
two values:
gosh[r7rs.user]$ (define (display-pair s r) (display s) (display " + ") (display r) (newline)) <1> display-pair gosh[r7rs.user]$ (define display-sqrt (compose-left exact-integer-sqrt display-pair)) <2> display-sqrt gosh[r7rs.user]$ (display-sqrt 3) 1 + 2
- Define a procedure which takes two values and displays them.
-
Now compose this with
exact-integer-sqrt
, which returns multiple values.
- Definition
(compose-right procedure-1 ...)
- Description
-
procedure-1
- the procedures should accept an appropriate number
of arguments and return multiple values so the chain is well formed.
Constructs a right-composition of its arguments, so that:
((compose-right f g h) args) #| is equivalent to |# (apply/mv f (apply/mv g (apply h args)))
- Examples
compose-right
is the same as compose-left
, but with the order of arguments
reversed. The following example composes the multiple-value returning
exact-integer-sqrt
with a procedure to display the two values:
gosh[r7rs.user]$ (define display-sqrt (compose-right display-pair exact-integer-sqrt)) display-sqrt gosh[r7rs.user]$ (display-sqrt 3) 1 + 2
Different Schemes handle multiple values in some contexts in different ways.
For example, Gauche permits multiple-values to be used in single-value contexts:
gosh$ (+ (values 1 2) (values 3 4)) <1> 4 gosh$ (map (lambda (a) (values a a)) '(1 2 3)) (1 2 3)
- Notice how adding two sets of multiple values together only deals with the first value in each pair of values.
But this is not required behaviour, and indeed the R7RS report states that, for map
, it is an
error if the mapped procedure does not return a single value.
Chez Scheme gives errors in both cases:
Chez Scheme Version 9.5.8 Copyright 1984-2022 Cisco Systems, Inc. > (+ (values 1 2) (values 3 4)) Exception: returned two values to single value return context > (map (lambda (a) (values a a)) '(1 2 3)) Exception: returned two values to single value return context
Kawa only gives an error in the first case, but not the second:
#|kawa:1|# (+ (values 1 2) (values 3 4)) java.lang.ClassCastException: class gnu.mapping.Values$Values2 cannot be cast to class gnu.math.Numeric #|kawa:2|# (map (lambda (a) (values a a)) '(1 2 3)) (1 1 2 2 3 3)