Newsgroups: comp.lang.scheme Path: utzoo!utgpu!news-server.csri.toronto.edu!torsqnt!snitor!doug From: doug@snitor.uucp (Doug Moen) Subject: Multiple return values Message-ID: <1991Apr1.181633.12561@snitor.uucp> Organization: Siemens Nixdorf References: <1991Mar26.155905.12906@daffy.cs.wisc.edu> Date: Mon, 1 Apr 1991 18:16:33 GMT carlton@husc10.harvard.edu (david carlton) writes: >What do people think about the suggestion to have procedures such as >the mutation procedures that return an unspecified value in fact return >no value at all? This was mentioned as a possibility in the proposal >to add multiple return values for Scheme, and T seems to be leaning in >that direction. I'm not sure what I think about it or, for that >matter, about the whole multiple return value concept - while there >are often situations where I want to return multiple values, it is >hard for me to think of good notation for that sort of thing. 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 ) ) 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. The `conceptual problem' caused by using lists to represent multiple return values is also a non-issue. Conceptually, a set of return values is a 'tuple'. A tuple is an ordered set of elements of different types, addressed by position. It is common practice to use lists in Scheme to represent tuples; the Scheme standard even provides procedures like cadr and caddr to support this practice. You are using lists as tuples whenever you use 'apply' to invoke a procedure that takes a fixed number of arguments. When Pavel says 'values ... should represent conceptual wholes' in connection with lists, he is thinking about the other common use of lists in Scheme, which is to represent arrays of values of the same type. (By 'same type', I mean that all of the values in the list are being used in the same way.) Scheme procedures such as reverse and member support the use of lists as 'arrays'. 'Tuples' are just as legitimate an abstraction as 'arrays', and I think it is legitimate for Scheme programmers to use lists to represent either of these abstractions. -- Doug Moen | doug@snitor.uucp | uunet!snitor!doug | doug.tor@sni.de (Europe)