by Marc Nieper-Wißkirchen
This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-210@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
bind/list
implementation to satisfy tail-call requirement.)multiple-value-call
to refer to it as a special operator instead of a function.)This SRFI extends the Scheme standard with procedures and syntax dealing with multiple values, including syntax to create lists and vectors from expressions returning multiple values and procedures returning the elements of a list or vector as multiple values.
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.
Despite this fact, programming with multiple values is more cumbersome
than programming with single values. This is mostly due to the fact
that Scheme's application syntax does not deal directly with operands
returning multiple values, so that the programmer has to fall back on
things like call-with-values
.
To make programming with multiple values appear more natural, convenient and efficient, this SRFI defines procedures and syntax for dealing with multiple values.
In
the R5RS,
there were basically two procedures dealing with multiple
values, values
and call-with-values
. SRFI
8 and
SRFI
11 added binding constructs for multiple values, yet these
do not cover all most-used idioms when dealing with multiple
values. Thus the need for this complementary SRFI.
Some of the syntax and procedures defined here also appear
in SRFI
71, albeit under different names. SRFI 71, however, is
mostly concerned with extending the let
keyword.
To implement it, one needs a mechanism to change this keyword.
When this specification talks about boxes, it refers to the disjoint Scheme type described by SRFI 111 (single-valued) and SRFI 195 (multiple-valued).
(apply/mv operator operand1 … producer)
Syntax: Operator
,
each operand
, and producer
can be
any expression.
Semantics: An apply/mv
expression is
evaluated as follows: Operator
,
each operand
, and producer
are
evaluated in an unspecified order. The procedure resulting from
evaluating operator
is then tail-called with the
single values of the operand
s and the multiple
values of producer
in that order as the actual arguments. The
values returned by the procedure are returned.
The apply/mv
syntax could be defined by
(define-syntax apply/mv (syntax-rules () ((apply/mv operator operand1 ... producer) (letrec-syntax ((aux (syntax-rules ::: () ((aux %operator () ((%operand1 arg1) :::) %producer) (let-values (((proc) %operator) ((arg1) %operand1) ::: (args %producer)) (apply proc arg1 ::: args))) ((aux %operator (%operand1 operand2 :::) (temp :::) %producer) (aux %operator (operand2 :::) (temp ::: (%operand1 arg1)) %producer))))) (aux operator (operand1 ...) () producer)))))
A trick is used to generate the temporary names needed to avoid specifying the order in which the values are evaluated.
(apply/mv string #\a (values #\b #\c)) ⟹ "abc"
(call/mv consumer producer1 …)
Syntax: Consumer
and each producer
can be
any expression.
Semantics: A call/mv
expression is
evaluated as follows: Consumer
and
each producer
are evaluated in an unspecified
order. The procedure resulting from
evaluating consumer
is then tail-called with the
multiple values of the producer
s in that order as
the actual arguments. The values returned by the procedure are
returned.
Note: This syntax is equivalent to the Common Lisp
special operator multiple-value-call
.
The call/mv
syntax could be defined by
(define-syntax call/mv (syntax-rules () ((call/mv consumer producer1 ...) (letrec-syntax ((aux (syntax-rules ::: () ((aux %consumer () ((%producer1 args1) :::)) (let-values (((proc) %consumer) (args1 %producer1) :::) (apply proc (append args1 :::)))) ((aux %consumer (%producer1 producer2 :::) (temp :::)) (aux %consumer (producer2 :::) (temp ::: (%producer1 args1))))))) (aux consumer (producer1 ...) ())))))
(call/mv string (values #\a #\b) (values #\c #\d)) ⟹ "abcd"
(list/mv element1 … producer)
Syntax: Each element
and
producer
can be any expression.
Semantics: A list/mv
expression is
evaluated as follows: Each element
and producer
are evaluated in an unspecified
order. A list constructed from the
single values of the element
s and the multiple
values of producer
in that order is then returned.
The list/mv
syntax could be defined by
(define-syntax list/mv (syntax-rules () ((list/mv element1 ... producer) (apply/mv list element1 ... producer))))
(list/mv 'a (values 'b 'c)) ⟹ (a b c)
(vector/mv element1 … producer)
Syntax: Each element
and
producer
can be any expression.
Semantics: A vector/mv
expression is
evaluated as follows: Each element
and producer
are evaluated in an unspecified
order. A vector constructed from the
single values of the element
s and the multiple
values of producer
in that order is then returned.
The vector/mv
syntax could be defined by
(define-syntax vector/mv (syntax-rules () ((vector/mv element1 ... producer) (apply/mv vector element1 ... producer))))
(vector/mv 'a (values 'b 'c)) ⟹ #(a b c)
(box/mv element1 … producer)
Syntax: Each element
and
producer
can be any expression.
Semantics: A box/mv
expression is evaluated
as follows: Each element
and producer
are evaluated in an unspecified
order. A
(SRFI
195) box is constructed from the single values of
the element
s and the multiple values
of producer
in then order is then returned.
The box/mv
syntax could be defined by
(define-syntax box/mv (syntax-rules () ((box/mv element1 ... producer) (apply/mv box element1 ... producer))))
(unbox (box/mv 'a (values 'b 'c))) ⟹ a b c
(value/mv index operand1 … producer)
Syntax: Index
,
each operand
, and producer
can be
any expression.
Semantics: A value/mv
expression is
evaluated as follows: Index
,
each element
and producer
are
evaluated in an unspecified order. It is an error if the
value i of index
is not an exact
non-negative index. The ith element of the list
consisting of the single values of the operand
s
and the multiple values of producer
in that order is then
returned. It is an error if there is no such element.
The value/mv
syntax could be defined by
(define-syntax value/mv (syntax-rules () ((value/mv index operand1 ... producer) (apply/mv value index operand1 ... producer))))
(value/mv 1 'a (values 'b 'c)) ⟹ b
(coarity producer)
Syntax: Producer
can be any
expression.
Semantics: An coarity
expression is
evaluated as follows: Producer
is evaluated and
then the number of resulting values is returned.
The coarity
syntax could be defined by
(define-syntax coarity (syntax-rules () ((coarity producer) (let-values ((res producer)) (length res)))))
(coarity (values 'a 'b 'c)) ⟹ 3
(set!-values formals producer)
Syntax: formals
is a formal arguments list
and producer
can be any expression.
Semantics: A set!-values
expression is
evaluated as follows: Producer
is evaluated and the
resulting values are stored in the locations to which the
variables of the formals
are bound, where
the formals
are matched to the resulting values in
the same way that the formals
in
a lambda
expression are matched to the arguments in
a procedure call. The result of the set!-values
expression is unspecified.
The set!-values
syntax could be defined by
(define-syntax set!-values (syntax-rules () ((set!-values (var1 ...) producer) (letrec-syntax ((aux (syntax-rules ::: () ((aux () ((%var1 temp1) :::) %producer) (let-values (((temp1 ::: . temp*) %producer)) (set! %var1 temp1) :::)) ((aux (%var1 var2 :::) (temp :::) %producer) (aux (var2 :::) (temp ::: (%var1 temp1)) %producer))))) (aux (var1 ... ) () producer))) ((set!-values (var1 ... . var*) producer) (letrec-syntax ((aux (syntax-rules ::: () ((aux () ((%var1 temp1) ::: (%var* temp*)) %producer) (let-values (((temp1 ::: . temp*) %producer)) (set! %var1 temp1) ::: (set! %var* temp*))) ((aux (%var1 var2 :::) (temp :::) %producer) (aux (var2 :::) (temp ::: (%var1 temp1)) %producer))))) (aux (var1 ... var*) () producer))) ((set!-values var* producer) (let-values ((temp*) producer) (set! var* temp*)))))
(let ((x #f) (y #f)) (set!-values (x . y) (values 'a 'b)) (list x y)) ⟹ (a (b))
(with-values producer consumer)
Syntax: Producer
and consumer
can be any expressions.
Semantics: A with-values
expression is
evaluated as follows: Producer
and consumer
are evaluated in an unspecified order.
The procedure resulting from evaluating consumer
is
then tail-called with the multiple values
of producer
as the actual arguments, and its return
values are returned.
The with-values
syntax could be defined by
(define-syntax with-values (syntax-rules () ((with-values producer consumer) (apply/mv consumer producer))))
(with-values (values 4 5) (lambda (a b) b)) ⟹ 5
(case-receive producer clause …)
Syntax: producer
can be any expression
and each clause
is of the form (formals
body)
as in a case-lambda
expression.
Semantics: A case-receive
expression is
evaluated as follows: Producer
is evaluated and
then the first clause for which the resulting values agree with
formals
is selected, where agreement is specified
as for the formals
of a lambda
expression. The variables of formals
are bound
to fresh locations, the values are stored in those locations,
the body
is evaluated in the extended
environment, and the results of body
are returned
as the results of the case-receive
expression.
The case-receive
syntax could be defined by
(define-syntax case-receive (syntax-rules () ((case-receive producer clause ...) (with-values producer (case-lambda clause ...)))))
(case-receive (values 'a 'b) ((x) #f) ((x . y) (list x y))) ⟹ (a (b))
(bind/mv producer transducer1 …)
Syntax: Producer
and
the transducers
can be any expressions.
Semantics: A bind/mv
expression is
evaluated as follows: Producer
and
the transducers
are evaluated in an unspecified
order. The procedure resulting from
evaluating transducer1
is then applied
to the values resulting from
evaluating producer
. The resulting values are
then applied to transducer2
, and so on
until the final transducer
is tail-called and the
resulting values are returned as the result of
the bind/mv
expression.
The bind/mv
syntax could be defined by
(define-syntax bind/mv (syntax-rules () ((bind/mv producer transducer ...) (bind/list (list/mv producer) transducer ...))))
(bind/mv (values 1 2 3) (map-values (lambda (x) (* 2 x))) (map-values (lambda (x) (+ 1 x)))) ⟹ 3 5 7
(list-values list)
It is an error if list
is not a list.
The list-values
procedure yields the elements
of list
as multiple values. It could be
defined by
(define (list-values lis) (apply values lis))
(list-values '(a b c)) ⟹ a b c
(vector-values vec)
It is an error if vec
is not a vector.
The vector-values
procedure yields the elements
of vec
as multiple values. It could be
defined by
(define (vector-values vec) (list-values (vector->list vec)))
(vector-values #(a b c)) ⟹ a b c
(box-values box)
It is an error if box
is not a box.
The box-values
procedure yields the elements
of box
as multiple values. Equivalent to unbox
.
It could be defined by
(define box-values unbox)
(box-values (box 'a 'b 'c)) ⟹ a b c
(value index obj0 … objn-1)
It is an error if index
is not an exact non-negative
integer less than n
.
The value
procedure simply returns its
argument objindex
. It could be defined by
(define (value k . objs) (list-ref objs k))
(value 1 'a 'b 'c) ⟹ b
(identity obj1 …)
The identity
procedure yields the
objects objs
as multiple values.
Equivalent to values
. It could
be defined by
(define identity values)
(identity 1 2 3) ⟹ 1 2 3
(compose-left transducer1 …)
It is an error if the transducers
are not procedures.
The compose-left
procedure yields the functional
left composition of the transducers
. When
the resulting procedure is applied to arguments, these arguments
are passed to transducer1
,
whose results are passed
to transducer2
, and so on
until the final results are returned. It could be defined by
(define compose-left (case-lambda (() identity) ((transducer . transducers) (let f ((transducer transducer) (transducers transducers)) (if (null? transducers) transducer (let ((composition (f (car transducers) (cdr transducers)))) (lambda args (apply/mv composition (apply transducer args)))))))))
(let ((f (map-values (lambda (x) (* 2 x)))) (g (map-values (lambda (x) (+ x 1))))) ((compose-left f g) 1 2 3)) ⟹ 3 5 7
(compose-right transducer1 … transducern)
It is an error if the transducers
are not procedures.
The compose-right
procedure yields the functional
right composition of the transducers
. When
the resulting procedure is applied to arguments, these arguments
are passed to transducern
,
whose results are passed
to transducern-1
, and so on
until the final results are returned. It could be defined by
(define compose-right (case-lambda (() identity) ((transducer . transducers) (let f ((transducer transducer) (transducers transducers)) (if (null? transducers) transducer (let ((composition (f (car transducers) (cdr transducers)))) (lambda args (apply/mv transducer (apply composition args)))))))))
(let ((f (map-values (lambda (x) (* 2 x)))) (g (map-values (lambda (x) (+ x 1))))) ((compose-right f g) 1 2 3)) ⟹ 4 6 8
(map-values proc)
It is an error if proc
is not a
procedure.
The map-values
procedure returns a procedure that
accepts an arbitrary number of arguments,
applies proc
to each of them in an
unspecified order and returns the resulting single values as
multiple values. It could be defined by
(define (map-values proc) (lambda args (list-values (map proc args))))
((map-values odd?) 1 2 3) ⟹ #t #f #t
(bind/list list transducer1 …)
It is an error if list
is not a list
or the transducers
are not
procedures.
The bind/list
procedure passes
the elements of list
to transducer1
, whose results
are then passed
to transducer2
, and so on until
the final transducer
is tail-called and the
resulting values are returned. It could be defined by
(define bind/list (case-lambda ((lis) (list-values lis)) ((lis transducer) (apply transducer lis)) ((lis transducer . transducers) (apply bind/list (list/mv (apply transducer lis)) transducers))))
(bind/list '(1 2 3) (map-values (lambda (x) (* 3 x)))) ⟹ 3 6 9
(bind/box box transducer1 …)
It is an error if box
is not a box
or the transducers
are not
procedures.
The bind/box
procedure passes the values of
box
to transducer1
, whose results
are then passed
to transducer2
, and so on until
the final tranducer
is tail-called and the
resulting values are returned. It could be defined by
(define (bind/box bx . transducers) (apply bind/list (list/mv (unbox bx)) transducers))
(bind/box (box 1 2 3) (map-values (lambda (x) (* 3 x)))) ⟹ 3 6 9
(bind obj transducer1 …)
It is an error if the transducers
are
not procedures.
The bind
procedure passes
the obj
to transducer1
, whose results
are then passed
to transducer2
, and so on until
the final tranducer
is tail-called and the
resulting values are returned. It could be defined by
(define (bind obj . transducers) (apply bind/list (list obj) transducers))
(bind 1 (lambda (x) (values (* 3 x) (+ 1 x)))) ⟹ 3 2
A portable R7RS implementation can be found in the official repository of this SRFI and can also be deduced from the complete definitions of all syntax and procedures given above.
Implementers are highly encouraged to distribute specialized implementations with their Schemes that implement the described syntax and procedures in the most efficient way.
A discussion on the SRFI 195 mailing list eventually led to this SRFI. I would like to thank all participants of this discussion for their contributions.
John Cowan suggested adding the equivalent of Common Lisp's
procedure multiple-value-call
.
Jim Rees read the example code in the first draft carefully.
Wolfgang Corcoran-Mathe made an extensive review and shared his helpful comments on the mailing list.
The idea of the with-values
form comes from the
paper An Efficient Implementation of Multiple
Return Values in Scheme by J. Michael Ashley and
R. Kent Dybvig.
© 2020 Marc Nieper-Wißkirchen.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.