Reputation: 1681
It seems like in order to use multiple return values in Racket, I have to either use define-values
or collect them into a list with (call-with-values (thunk (values-expr)) list)
. In the latter case, why would someone to choose to return multiple values instead of a list, if just have to collect them into a list anyway? Additionally, both of these are very wordy and awkward to work into most code. I feel like I must be misunderstanding something very basic about multiple-return-values. For that matter, how do I write a procedure accepting multiple return values?
Upvotes: 21
Views: 10472
Reputation: 52419
A number of answers here show how to use match
to decompose a returned list - but you can also do the same with multiple returned values, using match/values
and some other forms like match-define-values
, match-let-values
and company.
; demonstrate returning and matching complex values
(define (f) (values (list 1 2) 3))
(match/values (f)
[((list a b) c) (+ a c)]) ; 4
(match-define-values ((list a b) c) (f))
(println a) ; 1
Upvotes: 0
Reputation: 71065
Racket doc gives us the quintessential example why, in disguise:
> (let-values ([(q r) (quotient/remainder 10 3)])
(if (zero? r)
q
"3 does *not* divide 10 evenly"))
"3 does *not* divide 10 evenly"
We get two values directly, and use them separately in a computation that follows.
update: In Common Lisp, with its decidedly practical, down-to-the-metal, non-functional approach (where they concern themselves with each extra cons cell allocation), it makes much more sense, especially as it allows one to call such procedures in a "normal" way as well, automatically ignoring the "extra" results, kind of like
(let ([q (quotient/remainder 10 3)])
(list q))
But in Racket this is invalid code. So yeah, it looks like an extraneous feature, better to be avoided altogether.
Upvotes: 5
Reputation: 16260
Although I may be missing some of the Scheme history and other nuances, I'll give you my practical answer.
First, one rule of thumb is if you need to return more than 2 or 3 values, don't use multiple values and don't use a list. Use a struct
. That will usually be easier to read and maintain.
Racket's match
forms make it much easier to destructure a list return value -- as easy as define-values
:
(define (f)
(list 1 2))
(match-define (list a b) (f))
(do-something-with a b)
;; or
(match (f)
[(list a b) (do-something-with a b)])
If you have some other function, g
, that takes a (list/c a b)
, and you want to compose it with f
, it's simpler if f
returns a list. It's also simpler if both use a two-element struct
. Whereas call-with-values
is kind of an awkward hot mess, I think.
Allowing multiple return value is an elegant idea, because it makes return values symmetric with arguments. Using multiple values is also faster than lists or structs (in the current implementation of Racket, although it could work otherwise).
However when readability is a higher priority than performance, then in modern Racket it can be more practical to use a list
or a struct
, IMHO. Having said that I do use multiple values for one-off private helper functions.
Finally, there's a long, interesting discussion on the Racket mailing list.
Upvotes: 11
Reputation: 18917
values
is handy because it
For example, using
(define (out a b) (printf "a=~a b=~a\n" a b))
then
(let ((lst (list 1 2 3)))
(let ((a (first lst)) (b (second lst))) ; destructure
(out a b)))
will work even though lst
has 3 elements, but
(let-values (((a b) (values 1 2 3)))
(out a b))
will not.
If you want the same control and destructuring with a list, you can however use match
:
(let ((lst (list 1 2)))
(match lst ((list a b) (out a b))))
Note that he creation of the structure, e.g. (list 1 2)
vs (values 1 2)
is equivalent.
Upvotes: 4
Reputation: 48745
Using list
as the consumer defeats the purpose of multiple values so in that case you could just have used lists to begin with. Multiple values is actually a way of optimization.
Semanticly returning a list and several values are similar, but where you return many values in a list effort goes into creation of cons cells to make the list and destructuring accessors to get the values at the other end. In many cases however, you wouldn't notice the difference in performance.
With multiple values the values are on the stack and (call-with-values (lambda () ... (values x y z)) (lambda (x y z) ...)
only checks the number to see if it's correct.. If it's ok you just apply the next procedure since the stack has it's arguments all set from the previous call.
You can make syntactic sugar around this and some popular ones are let-values
and SRFI-8 receive is a slightly simpler one. Both uses call-with-values
as primitive.
Upvotes: 4