Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!usc!snorkelwacker.mit.edu!bloom-beacon!dont-send-mail-to-path-lines From: mkatz@garlic.stanford.EDU (Morris Katz) Newsgroups: comp.lang.scheme Subject: Multiple return values Message-ID: <9104021918.AA18763@garlic.Stanford.EDU> Date: 2 Apr 91 19:18:20 GMT References: <1991Apr1.181633.12561@snitor.uucp> Sender: daemon@athena.mit.edu (Mr Background) Organization: The Internet Lines: 119 Date: Mon, 1 Apr 1991 18:16:33 GMT From: Doug Moen Organization: Siemens Nixdorf In the latest issue of LISP Pointers, Pavel Curtis (Pavel@Xerox.Com) discusses the Scheme multiple return value proposal. The proposal introduces 3 changes to Scheme: 1. (values x ...) The procedure 'values' takes an arbitrary number of arguments, including none, and returns all of these arguments as its results. 2. (call-with-values producer consumer) Invoke the procedure 'producer' with no arguments, then pass all of the values returned as arguments to 'consumer'. 3. Continuations can now take any number of arguments, including none. call-with-values is rather inconvenient to use directly; some sort of syntactic sugar is needed. Curtis describes and rejects a new form 'bind-values', which is similar to muliple-value-bind in Common Lisp. He then describes a better solution: 'We are thus considering allowing a list of variables to appear in place of a single one in let and let* expressions: (let* ((a (foo)) (b (bar a)) ((c d) (baz a b)) (e (mumble a b c d))) (frotz a b c d e)) I have a counter-proposal. I feel there is a much simpler way to support multiple return values; one which fits in better with the rest of the language: multiple return values are represented by lists. Thus: (values x y ...) is replaced by (list x y ..) (call-with-values p c) is replaced by (apply c (p)) Finally, I would extend let and let* so that in place of a variable, any of the forms allowed in the first argument to lambda can be used. Thus: (let ((a (foo)) ((b c) (procedure-which-returns-a-list-of-two-values)) ((first second . rest) (procedure-which-returns-a-list))) ...) This extension to let introduces the following symmetry into the language: (let (( )) ) is now equivalent to (apply (lambda ) ) Ilike this extension to let, but I believe that it is completely independent of the question as to whether muliple values are represented as lists. I think my proposal has two advantages over the one described by Pavel: 1. It is simpler. No fundamentally new mechanisms need to be added to the language; the only language change is a simple generalization of let and let*. 2. It is more powerful. My proposed extension to let makes it easier to use procedures which represent multiple return values by a list; IN ADDITION, the new let syntax can be used to simplify code which disassembles list structure. Also, Scheme provides a rich set of operations on lists. Any of these operations can be used on the value returned by a procedure that adheres to the multiple-return-values-are-lists convention. Perhaps I should claim that my proposal is more synergistic. Curtis Pavel supplies two arguments against representing multiple return values as lists: 'In addition to being inefficient, though, this has conceptual problems. It could be argued that values in programs should represent conceptual wholes; in many cases, the collection of values returned by some procedure lack this coherence.' I don't find either of these arguments compelling. The `inefficiency' caused by using lists is probably minor, and in any case, efficiency has always taken second place to simplicity and expressive power in the Scheme design philosophy. Efficiency has only taken second place when there is a strong semantic reason for doing things in a way that is not the most efficient, and when there has been a belief that there is a fairly efficient means of implementing the less than most efficient semantics. In particular, the Scheme community has often selected a semantics which has significant special cases that can be implemented very efficiently so that the user only pays a performance cost when features offered by the less than most efficient mechanism are utilized. Call-with-current-continuation is a classic example of such a trade off. It is more powerful than catch and throw in Common-lisp, us less efficient to implement in the general case, but can often be implemented very efficiently when used for cases in which catch and throw would have sufficed. I believe that your proposal fails to meet this level of scrutiny. Finally, I have not seen addressed here the issue which actually sunk my multiple values proposal. (I have not read Pavel's article, so I do not know if he addresses the issue or not.) The disagreement revolved around whether there should be an arity? function which returns info about the arity of a procedure or reified continuation. There were several suggestions about how to handle arity: 1) (arity? proc number) - Returns #t if PROC can be called with NUMBER values. 2) (arity? proc) - Returns the number of values required by PROC. (rest? proc) - Returns #t if PROC has a rest argument 3) My suggestion was for (arity? proc) which returns 3 multiple values: the minimum number of values required by PROC, the maximum number of values accepted by PROC, not including the rest arg (this value would always equal the first value for implementation without optional args), and whether PROC expects a rest arg (either #t or #f). The Scheme community basically divided into 3 camps on the arity issue: 1) The arity question should not be askable (a small minority). 2) Acceptablity of a given arity should be askable, but a querry about the range of acceptable arities should not (case 1 above). 3) Full arity information should be retrievable (cases 2 and 3 above). For a complete recap of these arguements, see the archives of about 2 years ago. -------------------- Morry Katz katz@cs.stanford.edu --------------------