[racket] Multiple return values

459 views
Skip to first unread message

Zayr Okale

unread,
Dec 14, 2011, 5:54:35 AM12/14/11
to us...@racket-lang.org
Hello, everyone.

Can someone please explain to me multiple return values? Not what it does, I understand that much, but what is this feature for? In what situations is it useful?

David Van Horn

unread,
Dec 14, 2011, 7:56:15 AM12/14/11
to us...@racket-lang.org

It's useful in situations where several values are the result of a
single computation. A simple example of this situation is the quotient
and remainder of an integer division.

There's also a more philosophical argument for them which is that, for
symmetry, functions should be able to produce multiple results since
they may consume multiple inputs.

David
_________________________________________________
For list-related administrative tasks:
http://lists.racket-lang.org/listinfo/users

J. Ian Johnson

unread,
Dec 14, 2011, 9:00:11 AM12/14/11
to David Van Horn, us...@racket-lang.org
In CPS, n-return values just means your continuation is n-ary. You don't need to allocate cells to put all your return values in. I don't know if Racket's implementation doesn't need to allocate, but that's also a bonus.
-Ian

Matthias Felleisen

unread,
Dec 14, 2011, 9:15:33 AM12/14/11
to J. Ian Johnson, us...@racket-lang.org

CPS is for old people. Be careful.

See Dybvig's paper on measurable benefits (or lack thereof) for values and friends.

Grant Rettke

unread,
Dec 14, 2011, 10:52:18 AM12/14/11
to Zayr Okale, us...@racket-lang.org
http://www.wisdomandwonder.com/link/1794/returning-multiple-values-in-scheme

Racket has more forms right?

> _________________________________________________
>  For list-related administrative tasks:
>  http://lists.racket-lang.org/listinfo/users

--
http://www.wisdomandwonder.com/
ACM, AMA, COG, IEEE

John Clements

unread,
Dec 14, 2011, 2:39:51 PM12/14/11
to Matthias Felleisen, Racket Users List

On Dec 14, 2011, at 6:15 AM, Matthias Felleisen wrote:

>
> CPS is for old people. Be careful.
>
> See Dybvig's paper on measurable benefits (or lack thereof) for values and friends.

You're referring to 1994, "An efficient implementation of multiple return values in Scheme" ?

John

Zayr Okale

unread,
Dec 15, 2011, 6:39:04 AM12/15/11
to Tomasz Rola, us...@racket-lang.org
On 14/12/2011 21:20, Tomasz Rola wrote:
> I am not sure how much usable values/call-with-values are in Scheme. In
> Common Lisp, some functions return multiple values, for example floor:
>
> [1]> (floor 13 4)
> 3 ;
> 1
>
> In this case, one computation is performed to have two values, and user is
> free to use only one of them or both. She can also signal her intentions
> about ignoring some of the returned values with (declare (ignore ...)),
> which, I guess, should be used by compiler/interpreter to optimize things
> better.
>
> This "ignore" is very handy, like below:
>
> [3]> (decode-universal-time (get-universal-time))
> 43 ;
> 9 ;
> 18 ;
> 14 ;
> 12 ;
> 2011 ;
> 2 ;
> NIL ;
> -1
>
> Also, comparing to C, this would be done by passing pointer to C struct to
> the function, which would then fill it with days and month etc. So, I
> think multiple values could be used instead of passing/returning
> structures. From optimization point, it would be tricky to declare things
> like "I pass this structure here to you, but I only need a month number".
> And it would be tricky (and trashy) to have multitude of smaller
> functions, each for computing year, month, hour from number of seconds.
>
> Last but not least, this may serve as a way to help using information in
> place where it is needed:
>
> [4]> (read-line )
> aa aa
> "aa aa" ;
> NIL
>
> Here, read-line returns bot a line of text and a value signaling if there
> was EOF condition. Again, one can ignore EOF-value if this is what one
> really wants.
>
> Regards,
> Tomasz Rola
>
> --
> ** A C programmer asked whether computer had Buddha's nature. **
> ** As the answer, master did "rm -rif" on the programmer's home **
> ** directory. And then the C programmer became enlightened... **
> ** **
> ** Tomasz Rola mailto:tomas...@bigfoot.com **

I even do understand what multiple return values are useful for in CL:
"okay, the function calculates other potentially useful values anyway,
so no reason not to make them available".

Unfortunately, this scenario doesn't apply to Racket. And this is
exactly what prompted my question. Since one of the reasons behind
multiple return values is, as David Van Horn pointed out, symmetry with
multiple input values (function arguments), then why optional input
values are allowed, but optional output values aren't?

The situation when all the return values are of equal importance, yet
returning a struct or a list is not convenient is, IMHO, quite rare.

Markku Rontu

unread,
Dec 15, 2011, 7:03:45 AM12/15/11
to Zayr Okale, Tomasz Rola, us...@racket-lang.org
Named return values, ah the dream of symmetry.

-Markku

Stephan Houben

unread,
Dec 15, 2011, 8:15:01 AM12/15/11
to Zayr Okale, Tomasz Rola, us...@racket-lang.org
On 12/15/2011 12:39 PM, Zayr Okale wrote:
>
> Unfortunately, this scenario doesn't apply to Racket. And this is exactly what prompted my question. Since one of the reasons behind multiple return values is, as David Van
> Horn pointed out, symmetry with multiple input values (function arguments), then why optional input values are allowed, but optional output values aren't?

But they are supported!

(define-syntax first-value-only
(syntax-rules ()
((_ e)
(call-with-values (λ () e)
(λ (x . rest) x)))))

(printf "~a~%" (first-value-only (values 1 2 3)))

In fact, just like optional input values, they are supported but not the default.

Stephan

Tomasz Rola

unread,
Dec 15, 2011, 12:17:42 PM12/15/11
to Zayr Okale, us...@racket-lang.org
On Thu, 15 Dec 2011, Zayr Okale wrote:

> I even do understand what multiple return values are useful for in CL: "okay,
> the function calculates other potentially useful values anyway, so no reason
> not to make them available".
>
> Unfortunately, this scenario doesn't apply to Racket. And this is exactly what
> prompted my question. Since one of the reasons behind multiple return values
> is, as David Van Horn pointed out, symmetry with multiple input values
> (function arguments), then why optional input values are allowed, but optional
> output values aren't?

This question is probably better asked to people behind RnRS. I was unable
to give you any interesting examples with Scheme, because I didn't know
them. Actually, I was a bit unsure about this multiple values stuff
myself, as I had been reading R5RS some time ago (I am yet to find time
for R6RS, so maybe there is more about values, but I don't know right
now).

After I exchanged punches with CL, I've got a bit better understanding of
the issue. Or so I hope.

Your question sounded like one of more general nature, which is why I
allowed myself to do this CL/C intrusion here.

> The situation when all the return values are of equal importance, yet
> returning a struct or a list is not convenient is, IMHO, quite rare.

I don't want to bet on this :-) .

I think that in some cases you may emulate values by operating on list.
Like, return a list of values and later use apply.

However, once you have to produce list of specified size, it becomes
inconvenient, IMHO. Because no matter what you want, you have to make this
one specific list and later go through it to access the elements (this can
be optimized, but one shouldn't count on such happy end).

So I think using list/struct forces an overhead when later you want to
make use of the values. On the other hand, with values (again, sorry for
CL), one can have:

[2]> (multiple-value-bind (f r) (floor 130 11) (list f r))

(11 9)

This is from CL HyperSpec. Here, your data is "inserted" into your
namespace, and this can be paired with (declare (ignore ...)) to make it
perform better.

Situations like this can be rare in other languages, simply because for
mul-vals to work and make sense it requires a bit bigger ecosystem of
language constructs and, like above, assumption about how and where things
are going to be optimized. Above, I think the minimal approach would be to
analyze entire multiple-value-bind expression.

I keep an eye on Racket but I don't know it too well (just one
non-trivial program bettered with profiler and few smaller ad hoc pieces
over few years period), so I am unsure how much sense is there in using
values in it.

Regards,
Tomasz Rola

--
** A C programmer asked whether computer had Buddha's nature. **
** As the answer, master did "rm -rif" on the programmer's home **
** directory. And then the C programmer became enlightened... **
** **
** Tomasz Rola mailto:tomas...@bigfoot.com **

Tomasz Rola

unread,
Dec 14, 2011, 12:20:29 PM12/14/11
to Zayr Okale, Tomasz Rola, us...@racket-lang.org

I am not sure how much usable values/call-with-values are in Scheme. In

Regards,

Neil Toronto

unread,
Dec 15, 2011, 5:32:33 PM12/15/11
to us...@racket-lang.org
On 12/15/2011 05:03 AM, Markku Rontu wrote:
> Named return values, ah the dream of symmetry.

I think my mad-scientist advisor did something like this once. I think
it would be awesome to have this, and of course keyword arguments, as
first-class constructs in Racket.

(As far as I understand, the macros that expand lambdas with keyword
arguments would be a major PITA to write in C, and would be almost
certainly buggy for years. We'll need a bootstrapping compiler first.)

Anyway, back to the question. Symmetry + GENERALITY is one main reason.
A first-class continuation acts a lot like a procedure. Generalizing a
bit, continuations should take multiple arguments. Invoking a
multiple-argument continuation is equivalent to returning multiple
values to the spot it was captured; we therefore need multiple values if
we want to generalize continuations.

Another reason is efficiency. I'm writing a simple ray tracer for 2D
image + depth-map effects. (Think shiny icons, logos and slideshows.)
Here's my code for extracting the min and max values from a depth map:

(define (flimage-extrema img)
(match-define (flimage vs d w h) img)
(for/fold ([v-min 0.0] [v-max 0.0]) ([v (in-flvector vs)])
(values (unsafe-flmin v-min v)
(unsafe-flmax v-max v))))

This, like all the other ray tracer code, has to run fast. In general,
getting good speed means 1) no boxing flonums; and more generally 2) no
allocating. If I didn't have multiple values I'd have three options:

A. Use mutation. Fast enough but undesirable. Makes code harder for
me and the compiler to reason about.
B. Put them in a pair or list. Slow. Allocates a container and boxes
the flonums `v-min' and `v-max' in every iteration.
C. Make two passes. Duplicates code and computations. Makes me cranky.

As it is, I believe Racket's JIT can prove that no flonums need boxing,
so this loop avoids allocation entirely.

Neil T

Sam Tobin-Hochstadt

unread,
Dec 15, 2011, 6:08:26 PM12/15/11
to Neil Toronto, us...@racket-lang.org
On Thu, Dec 15, 2011 at 5:32 PM, Neil Toronto <neil.t...@gmail.com> wrote:
> On 12/15/2011 05:03 AM, Markku Rontu wrote:
>>
>> Named return values, ah the dream of symmetry.
>
> I think my mad-scientist advisor did something like this once. I think it
> would be awesome to have this, and of course keyword arguments, as
> first-class constructs in Racket.

What would be the advantage of having keyword arguments implemented
the `compile' step, as opposed to implemented in the `expand' step as
they are now?
--
sam th
sa...@ccs.neu.edu

Zayr Okale

unread,
Dec 16, 2011, 6:52:32 AM12/16/11
to Tomasz Rola, us...@racket-lang.org
On 15/12/2011 21:17, Tomasz Rola wrote:
> On Thu, 15 Dec 2011, Zayr Okale wrote:
>
>> I even do understand what multiple return values are useful for in CL: "okay,
>> the function calculates other potentially useful values anyway, so no reason
>> not to make them available".
>>
>> Unfortunately, this scenario doesn't apply to Racket. And this is exactly what
>> prompted my question. Since one of the reasons behind multiple return values
>> is, as David Van Horn pointed out, symmetry with multiple input values
>> (function arguments), then why optional input values are allowed, but optional
>> output values aren't?
> This question is probably better asked to people behind RnRS. I was unable
> to give you any interesting examples with Scheme, because I didn't know
> them. Actually, I was a bit unsure about this multiple values stuff
> myself, as I had been reading R5RS some time ago (I am yet to find time
> for R6RS, so maybe there is more about values, but I don't know right
> now).

Technically, R6RS section 11.15 says the behavior is undefined in this
case. Racket could choose to silently ignore extra values while staying
compatible, and given that Racket considers #lang rNrs separate
languages from the default one, it is even less obliged to honor RnRS
requirement here. Not to mention this change won't break the existing code.

> After I exchanged punches with CL, I've got a bit better understanding of
> the issue. Or so I hope.
>
> Your question sounded like one of more general nature, which is why I
> allowed myself to do this CL/C intrusion here.

In some sense, it is. I can understand how and why to use multiple
values in CL, but I cannot directly apply this to Racket, since I am
prohibited from ignoring extra values without extra effort. To continue
the talk about symmetry, if the language doesn't support optional input
values and I want to add support for them via macros, I need to wrap one
or two things, that is, the function definition forms. If it doesn't
support optional output values, however, I have to wrap every function
call that uses them. This is O(n) vs O(1) effort, so to speak.

>> The situation when all the return values are of equal importance, yet
>> returning a struct or a list is not convenient is, IMHO, quite rare.
> I don't want to bet on this :-) .
>
> I think that in some cases you may emulate values by operating on list.
> Like, return a list of values and later use apply.
>
> However, once you have to produce list of specified size, it becomes
> inconvenient, IMHO. Because no matter what you want, you have to make this
> one specific list and later go through it to access the elements (this can
> be optimized, but one shouldn't count on such happy end).

On the other hand, allowing to ignore extra values doesn't seem to
create obvious optimizations problems and allows the same use while
allowing some more applications.

> So I think using list/struct forces an overhead when later you want to
> make use of the values. On the other hand, with values (again, sorry for
> CL), one can have:
>
> [2]> (multiple-value-bind (f r) (floor 130 11) (list f r))
>
> (11 9)
>
> This is from CL HyperSpec. Here, your data is "inserted" into your
> namespace, and this can be paired with (declare (ignore ...)) to make it
> perform better.

In the simplest cases in CL I can ignore extra values for free (without
extra wrappers or any other code clutter). In Racket I cannot, and this
is what prompted my question: why not do it the CL way?

> I keep an eye on Racket but I don't know it too well (just one
> non-trivial program bettered with profiler and few smaller ad hoc pieces
> over few years period), so I am unsure how much sense is there in using
> values in it.

I have the suspicion this behavior has been simply inherited from
Scheme, since PLT Scheme was originally a Scheme implementation (I
think). IIUC, PLT Scheme was renamed to Racket because the team was
feeling it isn't Scheme anymore. So, since it is now (more) free from
RnRS obligations, this feature can be implemented based on what's better
for Racket, as opposed to what RnRS says is better for Scheme. I feel
the only thing lost in the transition from RnRS way to CL way would be
the developers' time and effort spent implementing the change, while a
degree of convenience will be gained by all Racket users.

Neil Van Dyke

unread,
Dec 16, 2011, 7:22:41 AM12/16/11
to Zayr Okale, us...@racket-lang.org
Zayr Okale wrote at 12/16/2011 06:52 AM:
> In the simplest cases in CL I can ignore extra values for free
> (without extra wrappers or any other code clutter). In Racket I
> cannot, and this is what prompted my question: why not do it the CL way?

An ignored value of a multiple-value return could be a bug, rather than
the intention of the programmer. How can the compiler tell the
difference? How can someone reading the code later.

I am glad that Racket considers an ignored multiple-value return value
to be a bug.

A different question is whether an ignored single-value return value
should be considered a bug. Currently it is not, and a lot of code has
been written assuming that it is not.

--
http://www.neilvandyke.org/

Markku Rontu

unread,
Dec 16, 2011, 7:45:51 AM12/16/11
to Neil Van Dyke, us...@racket-lang.org
On Fri, Dec 16, 2011 at 2:22 PM, Neil Van Dyke <ne...@neilvandyke.org> wrote:
Zayr Okale wrote at 12/16/2011 06:52 AM:

In the simplest cases in CL I can ignore extra values for free (without extra wrappers or any other code clutter). In Racket I cannot, and this is what prompted my question: why not do it the CL way?

An ignored value of a multiple-value return could be a bug, rather than the intention of the programmer.  How can the compiler tell the difference?  How can someone reading the code later.

I am glad that Racket considers an ignored multiple-value return value to be a bug.
 
A different question is whether an ignored single-value return value should be considered a bug.  Currently it is not, and a lot of code has been written assuming that it is not.


Hmm, I don't get the logic. Ignored single return value is not a bug and is ok (because that's how things happened to be in that case in the first place) but ignored multiple-value is a bug (because that's how things happened to be in that case in the first place)? Ignored optional parameter could be bug but nobody is complaining for Racket not complaining about it? Of course there is no such thing as optional multiple-value return value ... as there is no named return value.

I would be happy if Racket by default would not consider these as bugs or not bugs, especially only because that's how things used to be, or how much legacy stuff there may be. Isn't that the purpose of PLT Scheme -> Racket transition that things can and will be improved? And Racket has contracts, tests and types, if someone wants to force or double-check something. Make it a platform where you can define your local ideals of style and enforce them automatically.

In fact I would be really glad also if someone came up with a neat syntax to support symmetric input-output with optionality and nameability :)

-Markku

 

Matthias Felleisen

unread,
Dec 16, 2011, 8:15:47 AM12/16/11
to Zayr Okale, us...@racket-lang.org

Does Racket blindly inherit from Scheme because of history?

No, Racketeers choose what to take over and what to leave behind.


Why are multiple values useful?

Multiple values enable a smooth functional style where pedestrian programmers may have to use clumsy package-unpackage or, worse, imperative style. This improvement shows up especially in loops. Here is a concrete example from a recent program I wrote:

> ;; Board ->* Natural [non-empty-Listof Player]
> ;; determines the number of winning territories and the players(s) who have that many territories
> ;; > (winners (list (territory 0 0 1 9 0) (territory 0 0 1 9 1)))
> ;; (values 2 '(0))
> ;; > (winners (list (territory 0 1 1 9 0) (territory 0 0 1 9 1)))
> ;; (values 1 '(0 1))
>

> (define (winners board) ;; <--- winners returns multiple values
> (for/fold ([best 0][winners '()]) ([p PLAYER#]) ;; <-- for/fold uses multiple values
> (define p-score (sum-territory board p))
> (cond [(> p-score best) (values p-score (list p))]
> [(< p-score best) (values best winners)]
> [(= p-score best) (values best (cons p winners))])))


Before someone had written it with lists and that had injected a mistake in the AI evaluation function of the game:

> ;; GameTree Natural -> Number
> ;; Returns a number that is the best move for the given player.
> (define (rate-position tree depth)
> (cond [(or (= depth 0) (no-more-moves? tree))
> (define-values (best w) (winners (game-board tree))) ;; <--- receive multiple values
> (if (member AI w) (/ 1 (length w)) 0)]
> [else
> (define ratings (rate-moves tree depth))
> (apply (if (= (game-player tree) AI) max min)
> (map second ratings))]))

You see, the code cleanly separates the winning score from the list of winners now. In turn, the AI function can easily compute the probability of winning at the end of this branch of the code. [[ This is a naive evaluation function. ]]

When the previous programmer had just used list, he forgot to extract the winning score from the list of winners -- and that meant the scoring was all wrong. Of course this is extremely difficult to notice during the game and the programmer conveniently also adjusted the tests to match the actually computed result.

Matthew Flatt

unread,
Dec 16, 2011, 9:47:52 AM12/16/11
to Markku Rontu, us...@racket-lang.org, Neil Van Dyke
At Fri, 16 Dec 2011 14:45:51 +0200, Markku Rontu wrote:
> Hmm, I don't get the logic. Ignored single return value is not a bug and is
> ok (because that's how things happened to be in that case in the first
> place) but ignored multiple-value is a bug (because that's how things
> happened to be in that case in the first place)?

That's not quite right. A `begin' ignores all values that are returned
by expressions preceding the last, and that's the case whether a given
expression returns a single or multiple values. So, that issue is not
about single versus multiple values, but about always ignored versus
not ignored.


At Thu, 15 Dec 2011 15:39:04 +0400, Zayr Okale wrote:
> Since one of the reasons behind
> multiple return values is, as David Van Horn pointed out, symmetry with
> multiple input values (function arguments), then why optional input
> values are allowed, but optional output values aren't?

Following up on Stephen's reply: If you provide too many arguments to a
function, then the extra arguments are not ignored; you get an
exception. Returning multiple values to a single-valued context is
treated the same way, raising an exception.

It sometimes happens that a higher-order function accepts a "callback"
argument that itself can take either N or M arguments. The function can
test whether the callback wants N or M arguments using
`procedure-arity-includes?'. To me, the right way to support functions
like Common Lisp's `floor' would be to allow the function to check the
arity of the current continuation, and then it can choose to return 1
or 2 values. That doesn't work currently, though; continuations claim
to accept any number of arguments.

As a practical matter, I find that the need for varying return arities
is rare enough that it's ok to just pick different names --- `floor'
vs. `floor*', say --- while the need for optional arguments is so
common that picking different names is painful. Better support for
varying result arities is something we can keep in mind, though.


If I had it all to do over again, I'd probably get rid of multiple
values and just have tuples. The compiler and run-time system would
cooperate to match tuple results with tuple receives to avoid
allocation much of the time, but a tuple would still count as a value.
That choice, of course, would move even further away from the idea that
extra result values can be ignored, and it would move away from a
symmetry between arguments and return values. I think it would work
better overall, but I'm not sure.

Jay McCarthy

unread,
Dec 16, 2011, 2:59:51 PM12/16/11
to Neil Toronto, us...@racket-lang.org
On Thu, Dec 15, 2011 at 3:32 PM, Neil Toronto <neil.t...@gmail.com> wrote:
On 12/15/2011 05:03 AM, Markku Rontu wrote:
Named return values, ah the dream of symmetry.

I think my mad-scientist advisor did something like this once. I think it would be awesome to have this, and of course keyword arguments, as first-class constructs in Racket.

--
Jay McCarthy <j...@cs.byu.edu>
Assistant Professor / Brigham Young University
http://faculty.cs.byu.edu/~jay

"The glory of God is Intelligence" - D&C 93

Markku Rontu

unread,
Dec 16, 2011, 4:47:54 PM12/16/11
to Matthias Felleisen, us...@racket-lang.org
On Fri, Dec 16, 2011 at 3:15 PM, Matthias Felleisen <matt...@ccs.neu.edu> wrote:

Does Racket blindly inherit from Scheme because of history?

No, Racketeers choose what to take over and what to leave behind.

Great, I'm waiting for the fruits of the labour with great anticipation and gratitude ;)
 

Why are multiple values useful?

Multiple values enable a smooth functional style where pedestrian programmers may have to use clumsy package-unpackage or, worse, imperative style. This improvement shows up especially in loops. Here is a concrete example from a recent program I wrote:


I'll keep this short since it's bed-time :)

Why are multiple return values better than e.g. lists? You can still mix the order of the return values. Only thing you get is guaranteed right number of values. But, this also makes your functions less flexible. You are not able to add any new values because every call site must be fixed, even if the change by itself is backwards compatible (e.g. only adding more values). Doesn't sound like a meaningful difference. However mixing collections and semantically different values is just bad design. Can't prevent people from shooting their own legs except maybe in classroom.

Typical case in e.g. Java is to have a pair of Request & Response classes for passing parameters and receiving return values. These are as close as Java can get to named parameters and return values. You will not be able to accidentally mix the order of the parameters or return values. You may have sensible optional / default values. You have co-,  contravariance and backwards compatibility for many changes of the interface. Sounds good, doesn't it? just the syntax is Java so you'll grow old typing your solution :) What do you think about that as a practice?

This was just something I think about these days. In Clojure side I use maps quite much with the predictable ups and downs. For Racket I might go with structs, if they had better support for immutability/copying, and I think the situation is improving in this area.

What I have in mind is for example something like this. calculate-statistics would return named return values of #:min and #:max and compiler would check that these are ok at the call sites. Now if the calculate-statistics also returns #:median then it doesn't matter that I don't use it in this case. It's not a bug.

(let# ((#:min min #:max max (calculate-statistics (list 1 2 3 4 1 5 2 3 5 0 3 11))))
    ; do something with the min and max
    (do-something min))

Possibly could get rid of the extra typing when you don't want/need to rename:

(let# ((min max (calculate-statistics (list 1 2 3 4 1 5 2 3 5 0 3 11))))
    ; do something with the min and max
    (do-something min))

(define (calculate-statistics data)
    ; do the work
    ...
    ; return some named values
    (return #:min min #:max max #:average average #:median median))

And again the predictable minimization:

(define (calculate-statistics data)
    ; do the work
    ...
    ; return some named values
    (return# min max average median))


I might try implementing something like that when I come up with a decent syntax.

-Markku
Reply all
Reply to author
Forward
0 new messages