What is the difference between values
and list
or cons
in Racket or Scheme? When is it better to use one over the other? For example, what would be the disadvantage if quotient/remainder
returns (cons _ _)
rather than (values _ _)
?
Back in 2002 George Caswell asked that question in comp.lang.scheme. The ensuing thread is long, but has many insights. The discussion reveals that opinions are divided.
https://groups.google.com/d/msg/comp.lang.scheme/ruhDvI9utVc/786ztruIUNYJ
My answer back then:
> What are the motivations behind Scheme's multiple return values feature?
> Is it meant to reflect the difference in intent, or is there a
> runtime-practical reason?
I imagine the reason being this.
Let's say that need f is called by g. g needs several values from f.
Without multiple value return, f packs the values in a list (or vector),
which is passed to g. g then immediately unpacks the list.
With multple values, the values are just pushed on the stack. Thus no
packing and unpacking is done.
Whether this should be called an optimization hack or not, is up to you.
--
Jens Axel Søgaard
We don't need no side-effecting We don't need no allocation
We don't need no flow control We don't need no special-nodes
No global variables for execution No dark bit-flipping for debugging
Hey! did you leave the args alone? Hey! did you leave those bits alone?
(Chorus) -- "Another Glitch in the Call", a la Pink Floyd
They are semantically the same in Scheme and Racket. In both you need to know how the return looks like to use it.
values
is connected to call-with-values
and special forms like let-values
are just syntax sugar with this procedure call. The user needs to know the form of the result to use call-with-values
to make use of the result. A return is often done on a stack and a call is also on a stack. The only reason to favor values
in Scheme would be that there are no overhead between the producer return and the consumer call.
With cons
(or list
) the user needs to know how the data structure of the return looks like. As with values
you can use apply
instead of call-with-values
to do the same thing. As a replacement for let-values
(and more) it's easy to make a destructuring-bind
macro.
In Common Lisp it's quite different. You can use values always if you have more information to give and the user can still use it as a normal procedure if she only wants to use the first value. Thus for CL you wouldn't need to supply quotient
as a variant since quotient/remainder
would work just as well. Only when you use special forms or procedures that take multiple values will the fact that the procedure does return more values work the same way as with Scheme. This makes values
a better choice in CL than Scheme since you get away with writing one instead of more procedures.
In CL you can access a hash like this:
(gethash 'key *hash* 't)
; ==> T; NIL
If you don't use the second value returned you don't know if T was the default value or the actual value found. Here you see the second value indicating the key was not found in the hash. Often you don't use that value if you know there are only numbers the default value would already be an indication that the key was not found. In Racket:
(hash-ref hash 'key #t)
; ==> #t
In racket failure-result
can be a thunk so you get by, but I bet it would return multiple values instead if values did work like in CL. I assume there is more housekeeping with the CL version and Scheme, being a minimalistic language, perhaps didn't want to give the implementors the extra work.
Edit: Missed Alexis' comment on the same topic before posting this
One oft-overlooked practical advantage of using multiple return values over lists is that Racket's compose
"just works" with functions that return multiple values:
(define (hello-goodbye name)
(values (format "Hello ~a! " name)
(format "Goodbye ~a." name)))
(define short-conversation (compose string-append hello-goodbye))
> (short-conversation "John")
"Hello John! Goodbye John."
The function produced by compose
will pass the two values returned by hello-goodbye
as two arguments to string-append
. If you're writing code in a functional style with lots of compositions, this is very handy, and it's much more natural than explicitly passing values around yourself with call-with-values
and the like.
compose
makes more sense. Document says "to provide symmetry with the fact that a procedure can accept multiple arguments." –
Cilo It's also related to your programming style. If you use values
, then it usually means you want to explicitly return n values. Using cons
, list
or vector
usually means you want to return one value which contains something.
There are always pros/cons. For values
: It may use less memory on some implemenentations. The caller need to use let-values
or other multiple values specific syntax. (I wish I could use just let
like CL.)
For cons
or other types: You can use let
or lambda
to receive the returning value. You need to explicitly deconstruct it to get the value you want using car
or other procedures.
Which to use and when? Again depending on your programming style and case by case but if the returning value can't be represented in one object (e.g. quotient and remainder), then it might be better to use values
to make the procedure's meaning clearer. If the returning value is one object (e.g. name and age for a person), then it might be better to use cons
or other constructor (e.g. record).
© 2022 - 2024 — McMap. All rights reserved.
values
is probably marginally faster because it performs less allocation. Functions that return multiple values can also be composed (withcompose
) in interesting ways, but I've rarely found that useful. Otherwise, I think it's probably pretty subjective, but I'm of the camp thatvalues
should probably usually be avoided. – Valuationcall-with-values
is the ultimate plug-in-your-own-continuation system. I've actually usedcase-lambda
withcall-with-values
on occasion, though I have not tried using keyword arguments! I think my reason for not trying the latter is that I don't believevalues
supports that: you'd have to directly call the continuation to use keywords. – Selfexistent