Matt G
Matt G

Reputation: 1681

How do I do anything with multiple return values in racket?

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

Answers (5)

Shawn
Shawn

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

Will Ness
Will Ness

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

Greg Hendershott
Greg Hendershott

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

uselpa
uselpa

Reputation: 18917

values is handy because it

  1. checks that the number of elements returned is correct
  2. destructures

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

Sylwester
Sylwester

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

Related Questions