doug@snitor.uucp (Doug Moen) (04/02/91)
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 ((<formals> <actuals>)) <body>) is now equivalent to (apply (lambda <formals> <body>) <actuals>) 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)
jinx@zurich.ai.mit.edu (Guillermo J. Rozas) (04/03/91)
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. There is another issue which your proposal does not satisfy, while Pavel's can be made to. I, and some other people, think that there is no reason (besides efficiency) nor is it particularly desirable to add multiple return values to Scheme unless new expressive power is gained. Certainly neither proposal adds any as stated. The form of additional expressive power that I have in mind is some form of that available in Common Lisp, by which additional return values can be dropped if not expected. In particular, I believe that it should be possible to re-define (define (quotient x y) (values (old-quotient x y) (old-remainder x y))) and have no-one who used the old version of QUOTIENT notice. Your proposal can't really accommodate that, while Pavel's can (although it does not require it). What you propose is a possible implementation of Pavel's proposal if no new expressive power is gained, but implementations that want to extend his proposal along these or other lines can do it with his but not yours.
gyro@cymbal.reasoning.COM (Scott Layson Burson) (04/03/91)
Date: Mon, 1 Apr 1991 18:16:33 GMT From: Doug Moen <snitor!doug@bloom-beacon.mit.edu> In the latest issue of LISP Pointers, Pavel Curtis (Pavel@Xerox.Com) discusses the Scheme multiple return value proposal. [...] 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 been using a slightly different syntax for years, e.g.: (let ((a b c (foo))) ...) I am very fond of this syntax. It has one fewer pair of parens than the one proposed, and also doesn't discriminate visually between the 1-value case and and the >1-value case -- some might find this a bug, but in a program full of multiple values I find it a feature. I have macros that implement this in Common Lisp and T. (Actually the syntax they implement contains an additional extension, which again I like a lot but I don't know if I would make a serious proposal of. The idea is that the order in which evaluation and binding are performed is indicated by depth of nesting of clauses, so that, e.g.: (let ((a b (foo)) ((c d e (bar a b)) ((f (zot b c e)))) (m n o (quux))) ...) is equivalent to (let ((a b (foo)) (m n o (quux))) (let ((c d e (bar a b))) (let ((f (zot b c e))) ...))) The idea here is that all clauses at a given depth are performed in parallel, and all clauses at depth n are done before/outside of all clauses at depth >n. I like this as it allows me essentially to mix the effects of LET and LET*, and also lets me supply something of a visual indication of the data flow (the way the example is written, for instance, is intended to convey that C, D, E, and F do not depend on M, N, and O, though this is only a convention, NOT a restriction enforced by the implementation).) Anyhow, maybe this latter preference of mine is a bit idiosyncratic, but I think the first proposal is something most everyone would like. 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. [...] I like to write in a style that makes liberal use of multiple values. I would be discouraged from doing so on efficiency considerations were they to be implemented as lists. I also agree with Pavel that there is a stylistic difference in the way the two are used. Consider the situation in C, where you can return several values from a function by packaging them up into a struct. I feel that if I have to declare a struct type solely for the benefit of the return value of a single function, that is more like "multiple values" than it is like a structure. Conversely, if that struct type is in use in many places already, then it would often make sense (in the Scheme case) to return it as a list, because much of the time I'm going to be manipulating the list as a unit rather than destructuring it (since there are already other functions that take lists like that as arguments). -- Scott
carlton@husc10.harvard.edu (david carlton) (04/03/91)
I guess that most of the questions that I have dealing with multiple return values are on how strictly the number of arguments returned should be enforced. That is, should the it be an error if the caller does not expect the number of values returned by the callee? There are, of course, two possibilities here: 1) Yes, they have to match. 2) No, they don't have to match. What are the pros and cons of each of these possibilities? The ones that I can think of are: 1) This is conceptually neater. After all, if the number of arguments in a function call has to match, then it is only sensible for the number of values in a function return to match. This also fits in quite well with the continuation passing way of looking at things, since function returns are function calls to the continuation. And it provides more error checking for your programs. 2) If they don't have to match then you can mimic the Common Lisp behaivour that Jinx wanted, namely to be able to drop unwanted additional return values. It is also a bit more efficient, requiring one fewer error check, but by the same token may open up another source of difficult-to-find errors. It allows procedures which currently return an unspecified value to return no value without breaking existing code, whereas requiring the number of arguments to match would screw up code which called such a function in a position requiring a value. That last statement itself needs elaboration - where would values be required in a Scheme program? (Assuming that we are following possibility #1, that checking this number is enforced.) In some cases, it is clear - for example, the first form in an 'if' statement should return one value. Presumably, all but the last form in a 'begin' statement should return no value. Though that itself is problematic - what if you call a function which performs a side effect _and_ returns a useful value? (append!, say.) Such a function clearly has to return a value, but you might also want to call it from the middle of a 'begin' statement. So perhaps it would be best to allow the forms in the middle of a 'begin' statement to return an arbitrary number of values. Similarly, the 'eval' part of a repl loop should be allowed to return an arbitrary number of values. (Never mind that the standard doesn't really admit the existence of a repl loop.) So what have I left out? Apologies if this posting seems a bit simplistic - I'm trying to figure out my opinion on these matters and the potential implications of my opinion as I write this, so it's not as polished as it might otherwise be. Which Schemes already implement multiple return values? T is the only one that I know of, but there must be more. What are the experiences of people using such systems? david carlton carlton@husc9.harvard.edu
tmb@ai.mit.edu (Thomas M. Breuel) (04/03/91)
In article <9104022333.AA00572@cymbal.reasoning.com.>, gyro@cymbal.reasoning.COM (Scott Layson Burson) writes: |> |> Date: Mon, 1 Apr 1991 18:16:33 GMT |> From: Doug Moen <snitor!doug@bloom-beacon.mit.edu> |> |> In the latest issue of LISP Pointers, Pavel Curtis (Pavel@Xerox.Com) |> discusses the Scheme multiple return value proposal. |> [...] |> |> 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. |> [...] |> |> I like to write in a style that makes liberal use of multiple values. I |> would be discouraged from doing so on efficiency considerations were |> they to be implemented as lists. I think it is wrong to add multiple return values to Scheme for efficiency reasons: the gain in efficiency is small to non-existent for good compilers, and the semantics are messy. In order to gain efficiency through the use of a special multiple return values data type, you have to seriously restrict how you can manipulate multiple return values (if you treat a set of multiple return values as a first class object, you might as well use lists, since the Scheme optimizer would face exactly the same problems in compiling this new data type efficiently). This adds lots of semantic hair to the language. And, the gains in execution speed are dubious at best. In fact, I suspect that since modern Scheme implementations have done away with the stack altogether and allocate all procedure activation frames on the heap, data structures representing multiple return values would simply end up on the heap as well. Also, in those cases where multiple return values could be allocated in a stack-like manner, a Scheme compiler stands a pretty good chance to eliminate heap allocation even when they are implemented using lists. A nice addition to Scheme that would make the use of lists as multiple return values a little more convenient would be some form of destructuring. I doubt that the Scheme committee as a whole could agree on destructuring binding for argument lists (which I would prefer), but a special form DESTRUCTURING-BIND or DLET would certainly be nice. Thomas.
jonl%kuwait@lucid.COM (Jon L White) (04/04/91)
In Scheme Digest V3 #175, Jinxs says of the Common Lisp MV style: Date: 2 Apr 91 18:57:22 GMT From: "Guillermo J. Rozas" <jinx@zurich.ai.mit.edu> . . . In particular, I believe that it should be possible to re-define (define (quotient x y) (values (old-quotient x y) (old-remainder x y))) and have no-one who used the old version of QUOTIENT notice. This is just an application of the principle of "referential transparency", which is THE reason why Common Lisp opted for a multiple-value syntax rather than fostering an embedding of multiple values into user-created structures like LISTs or VECTORs. There are times when this "transparency" causes hiccups -- like the dissimilarity between passing in "multiple arguments" to a function and receiving back "multiple values". After long experience with both styles of argument passing -- Interlisp's which simply dropped any extra arguments passed into a function and padded out with NIL's any formal parameters that lacked passed-in arguments, and MacLisp's which signaled "wrong number of arguments" errors -- the CL community decided that "transparency" on the callee's side was counterproductive (i.e., the client ought to know exactly the argument spectrum of the called function) but that "transparency" on the caller's side is extremely useful for functions like your QUOTIENT example. In short, being ignorant of extra returned information isn't nearly the disaster that being ignorant of some required input parameters is. -- JonL --
jinx@zurich.ai.mit.edu (Guillermo J. Rozas) (04/04/91)
There are a lot of issues in multiple values that haven't been mentioned. I like to classify them by code. Here are some of the more controversial points. A: Are the following two expressions equivalent? (values 1) 1 B: Is the following expression legal? (begin (values 1 2 3) 4) C: Is the following expression legal? (begin (values) 4) D: Is the following expression legal, and, if so, what does it return? (let ((x (values 1 2))) x) E: Is the following expression legal, and, if so, what does it return? (let ((x (values))) x) F: Is the following expression legal? (call-with-values (lambda () (values 1 2 3)) (lambda (x y) (list x y))) G: Is the following expression legal, and, if so, what does it return? (call-with-values (lambda () (values 1)) (lambda (x y) (list x y))) H: Does the following work? (call-with-values (lambda () (call-with-current-continuation (lambda (k) (k 1 2)))) (lambda (x y) (list x y))) PS: - By equivalent I mean that either can be substituted for the other without changing the meaning of the program (ignoring capture of free variables). - By legal I mean it is not in error. - By work I mean it is legal and the "obvious" value(s) is(are) returned. My preferences are: A: Yes. B: Yes. C: Yes. D: Yes, 1. E: No. F: No. G: No. H: Yes. But would accept a compromise where any of the above are left unspecified. My arm would need a lot of twisting to accept a proposal that requires any of those decisions to be otherwise. Although this set of choices may appear inconsistent, the crucial observation, that points out the asymmetry between APPLY and VALUES is that there are no implicit procedures, but there are implicit continuations. Furthermore, the continuation that VALUES is invoking is not apparent in the VALUES expression, but the (expression evaluating to) the procedure in APPLY expressions is. Given these asymmetries, the decisions need not be the same.
jinx@zurich.ai.mit.edu (Guillermo J. Rozas) (04/04/91)
In article <14592@life.ai.mit.edu> tmb@ai.mit.edu (Thomas M. Breuel) writes:
I think it is wrong to add multiple return values to Scheme for
efficiency reasons: the gain in efficiency is small to non-existent
for good compilers, and the semantics are messy.
I think you are mistaken about the efficiency issue. It obviously
depends on whether you use multiple values frequently or not, and if
you do, it is clear that the CONSING behavior of either CPS code or
multiple-values-as-lists is much worse than that of a properly
implemented multiple values facility.
I don't know what you mean by semantics. If you mean the naive
semantics, I think you are mistaken, because there are several simple
consistent semantics for multiple values that are easy to describe.
If you mean the denotational semantics of Scheme that appear at the
end of R3RS, you are also mistaken, since Will Clinger cleverly
arranged it so that only two items need to be modified to integrate
multiple values into the current language, namely the definition of
``single'', and the expansion of ``begin''.
In order to gain efficiency through the use of a special multiple
return values data type, you have to seriously restrict how you can
manipulate multiple return values (if you treat a set of multiple
return values as a first class object, you might as well use lists,
since the Scheme optimizer would face exactly the same problems in
compiling this new data type efficiently). This adds lots of semantic
hair to the language.
Your assumption that there is a new multiple-values data type is not
correct. A simple implementation leaves a special marker on the stack
when multiple values are expected, and VALUES checks for the presence
of the marker and puts the values in the correct registers or stack
locations. Thus the values are never consed on the heap with no need
for compiler smarts. There is no need for first-class VALUES objects,
nor are they particularly desirable.
And, the gains in execution speed are dubious at best. In fact, I
suspect that since modern Scheme implementations have done away with
the stack altogether and allocate all procedure activation frames on
the heap, data structures representing multiple return values would
simply end up on the heap as well. Also, in those cases where multiple
return values could be allocated in a stack-like manner, a Scheme
compiler stands a pretty good chance to eliminate heap allocation even
when they are implemented using lists.
You are also mistaken about what modern Scheme implementations do.
All of the modern compilers that I am familiar with try very hard to
use a stack, and resort to heap-allocated closures only when they
can't figure out what else to do.
A nice addition to Scheme that would make the use of lists
as multiple return values a little more convenient would be some
form of destructuring. I doubt that the Scheme committee as a whole
could agree on destructuring binding for argument lists (which I would
prefer), but a special form DESTRUCTURING-BIND or DLET would certainly
be nice.
Since we are supposedly close to agreeing on a macro facility, users
can write their favorite destructurers and make them publicly
available. I don't think that this is the sort of thing we should
standardize on.
oz@yunexus.yorku.ca (Ozan Yigit) (04/04/91)
In article <JINX.91Apr3160152@chamarti.ai.mit.edu> jinx@zurich.ai.mit.edu writes: >My arm would need a lot of twisting to accept a proposal that requires >any of those decisions to be otherwise. This is significant, no doubt, but what exactly is it that makes you or others involved in this discussion think that there is sufficient interest within the scheme community [at large] to add new syntax/semantics for multiple return values to the language in the first place? Is it a foregone conclusion that one of these proposals [or one that is yet to come] will necessarily be adopted? Just curious. oz --- What ought to disturb us are not mistakes | Internet: oz@nexus.yorku.ca in general, but only those of them that we | Uucp: utzoo/utai!yunexus!oz are powerless to correct. -- David Miller | Phone: 1+416-736-5257-33976
defrance@uni2a.unige.ch (04/04/91)
<snitor!doug@boom-beacon.mit.edu> (Doug Moen) debates in a previous posting about the Scheme multiple return value proposal and his counter-proposal. Here is my counter-counter-proposal: 1. (values x ...) The procedure 'values' takes an arbitrary number of arguments, including none, and returns all of these arguments as its results. 2. In a nested call, multiple values are bind to subsequent formal args: Suppose bar is a function with returns the three values 1 2 4 and foo a function with returns a b e.g. (define bar (lambda () (values 1 2 4)) (define foo (lambda () (values a b)) Then (+ (bar)) would return 7! That's quite powerful: Do you want the list of values? That's (list (bar)) But also (list (bar) (foo)) -> (1 2 4 a b) Only the first? (define firstval (lambda l (car l))) (firstval (foo)) -> a (firstval (bar) (foo)) -> 1 And also VERY elegant: (call-with-values producer consumer) is replaced by (consumer (producer)) (let (<formals> <actuals>) <body>) can still be replaced by ((lambda <formals> <body>) <actuals>) : (let ((a b c) (bar)) (* (+ a b) c)) -> 12 which is actually equivalent to ((lambda (a b c) (* (+ a b) c)) (bar)) -> 12 It would have at least two (minor?) disadvantages. The first is that since you can't tell how many values an application will return, Scheme has to wait until all the actual parameters are evalued before binding the formals, or it has to force the order of evaluation. The second is that current mono-valued standard procedures cannot be redefined as multiple values procedures without affecting compatibility with previously written code (That could be an advantage, actually). Maybe the Scheme community can tell me if something is wrong with this proposal. Anyway, since this seems to be the only way for everybody down here to suggest things to the Scheme deciders, I'd like to have as much echo as possible on the net, if you like this idea of multiple values. Bye -Massimo --------------------------------------------------------------------------------- MASSIMO DE FRANCESCO email: massimo@cuisun.uinige.ch Computer Science Center University of Geneva, SWITZERLAND ---------------------------------------------------------------------------------
pavel@parc.xerox.COM (Pavel Curtis) (04/05/91)
"Guillermo J. Rozas" writes: > There are a lot of issues in multiple values that haven't been > mentioned. I like to classify them by code. Here are some of the > more controversial points. I give below the answers for SchemeXerox. > A: Are the following two expressions equivalent? > (values 1) > 1 Yes. > B: Is the following expression legal? > (begin > (values 1 2 3) > 4) Yes. > C: Is the following expression legal? > (begin > (values) > 4) Yes. > D: Is the following expression legal, and, if so, what does it return? > (let ((x (values 1 2))) > x) No; it signals an error about a wrong number of arguments (to the implicit continuation). > E: Is the following expression legal, and, if so, what does it return? > (let ((x (values))) > x) No; same error as above. > F: Is the following expression legal? > (call-with-values > (lambda () > (values 1 2 3)) > (lambda (x y) > (list x y))) Ditto. > G: Is the following expression legal, and, if so, what does it return? > (call-with-values > (lambda () > (values 1)) > (lambda (x y) > (list x y))) Ditto. > H: Does the following work? > (call-with-values > (lambda () > (call-with-current-continuation > (lambda (k) > (k 1 2)))) > (lambda (x y) > (list x y))) Yes. > My preferences are: > A: Yes. > B: Yes. > C: Yes. > D: Yes, 1. > E: No. > F: No. > G: No. > H: Yes. Well, Jinx, what a wonder. We appear to disagree only on D, where you want to silently ignore extra returned values and SchemeXerox signals an error. Were we to agree to leave D unspecified, though, I'd want to do the same with E, F, and G: passing the wrong number of arguments to a continuation is passing the wrong number of arguments to a continuation. On the other hand, we could also say that the implicit single-argument continuations can have either of the following (implicit) argument list structures: (x) ; The SchemeXerox approach (x . ignored) ; The Jinx approach I could probably live with this compromise as well. Pavel Curtis
jonl%kuwait@lucid.COM (Jon L White) (04/05/91)
In Scheme Digest V3 #176 Date: 3 Apr 91 10:47:43 GMT From: "Thomas M. Breuel" <tmb@ai.mit.edu> A nice addition to Scheme that would make the use of lists as multiple return values a little more convenient would be some form of destructuring. I doubt that the Scheme committee as a whole could agree on destructuring binding for argument lists (which I would prefer), but a special form DESTRUCTURING-BIND or DLET would certainly be nice. In fact, Lisp/370 (predecessor to IBM's "Uncommon Lisp" called LISP/VM) had automatic destructuring in every lambda argument position, including the input arguments. By 1980, such an extension was working in parts of MacLisp and in VAX/NIL. In fact, it even "destructured" over vectors as well as lists; e.g.: (defun foo ((a #(b c) . l) &optional haha) ...) could be called like (foo '("Note This" #(dollars cents))) and the arguments would be bound thus: a = "Note This" b = dollars c = cents l = () This was particularly useful in the VAX/NIL compiler and assembler where much more use was made of simple vectors rather than lists. We even had a version that would destructure over DEFSTRUCT instances, in the expectation that "objects" like vectors and structures would displace lists as the more common data structure "of choice". There was no technical difficulty in extending these ideas for Common Lisp; but instead a debate arose over whether the pattern should be defined to be a data object (i.e., a cons cell implying taking the CAR and CDR of the input argument) or to be a program [i.e., for the above example, write `(,a `#(,b ,c) . ,l) instead of (a #(b c) . l)]. And in addition to this debate, there was an undercurrent of thinking that LET was sacred and primitive in it's historic, one-to-one definition. So that killed that. Too bad. I'm always having to intersperse lines of LET's amongst multiple incarnations of DESTRUCTURING-BIND, as well as with MULTIPLE-VALUE-BIND's. re: Date: 3 Apr 91 21:01:50 GMT From: "Guillermo J. Rozas" <jinx@zurich.ai.mit.edu> There are a lot of issues in multiple values that haven't been mentioned. I like to classify them by code. Here are some of the more controversial points. . . . Your particular suggestions look fine to me, but what's missing from the dialogue is the concept, elaborately worked out in the CL world, of constructs that (1) truncate multiple-values down to one value, and (2) "pass back" multiple values. For example, your case D easily falls out of one of the obvious rules here for (1) [unless there is to be a radical change to the syntax/semantics of LET, to accommodate either destructuring or multiple values.] -- JonL --
gumby@Cygnus.COM (David V. Wallace) (04/05/91)
Date: 4 Apr 91 21:01:00 GMT From: jonl%kuwait@lucid.COM (Jon L White) Your particular suggestions look fine to me, but what's missing from the dialogue is the concept, elaborately worked out in the CL world, of constructs that (1) truncate multiple-values down to one value, and (2) "pass back" multiple values. I always enjoyed the comment in the Chine Ual that read (for several editions) "Multiple values are not returned through an unwind-protect. I'd like to fix it but it's hard." When it finally was fixed that comment was sadly not replaced by any note indicating how difficult it in fact turned out to have been.
carlton@husc10.harvard.edu (david carlton) (04/05/91)
In article <JINX.91Apr3160152@chamarti.ai.mit.edu> jinx@zurich.ai.mit.edu (Guillermo J. Rozas) writes:
There are a lot of issues in multiple values that haven't been
mentioned. I like to classify them by code. Here are some of the
more controversial points.
...
E: Is the following expression legal, and, if so, what does it return?
(let ((x (values)))
x)
...
My preferences are:
E: No.
But would accept a compromise where any of the above are left unspecified.
My arm would need a lot of twisting to accept a proposal that requires
any of those decisions to be otherwise.
Although this set of choices may appear inconsistent, the crucial
observation, that points out the asymmetry between APPLY and VALUES is
that there are no implicit procedures, but there are implicit
continuations. Furthermore, the continuation that VALUES is invoking
is not apparent in the VALUES expression, but the (expression
evaluating to) the procedure in APPLY expressions is.
Given these asymmetries, the decisions need not be the same.
I think that I agree with you everywhere but here - I would probably
want to allow E as well, though it should be undefined what happens if
the value returned by the expression is ever used. This would nicely
solve the problem of what to do with the special forms and functions
that currently return undefined values - they could simply not return
a value. Of course, you could do that in any case, but then you would
break currently legal code that calls those functions from locations
whose continuations expect a single argument.
Not sure, though - I'm not even sure that I would want to allow
(let ((x (values 1 2)))
x)
david carlton
carlton@husc9.harvard.edu
kers@hplb.hpl.hp.com (Chris Dollin) (04/05/91)
It's interesting to watch this discussion develop. I have long detested the cack-handed way Common Lisp handles multiple values; this is, of course, because I have used a language which has handled multiple values since its inception, rather than having them grafted on at a later stage. That language is Pop11 (descended from Pop2). Now, it is clear that the way Pop handles multiple values (see shortly) is, as it stands, entirely unsuitable for Scheme; but insights from their use may be applicable. I'll try to leave my comments as unbiased as possible. Unlike CL, Pop multiple values can appear in any context; if a function returns multiple results, then the caller must be prepared to deal with them. This means that one cannot just extend an existing function with an extra result and expect old code to continue working. In practice, this does not seem to have many bad consequences; the addition of a single new name to the namespace (the new version of the function), with the ``old'' function being redefined as one which calls the new function and discards the result, is sufficient. Of course, if this happens several times, you may have to restructure your code, if you don't like the plethora of new names. However, there's more. As a consequence of the way multiple values are defined, (and of the way they're implemented), *multiple values accumulate*. Suppose that F takes one argument and delivers two results, G ditto, and H requires four arguments. Then (H (F x) (G y)) is sensible and applies H to the quartet of values returned by F and G. Someone discussed what would happen with BEGIN. An alternative not mentioned is that (BEGIN E1 ... En) would return *all* the results of the Ei (hmm ... just like a sequential version of VALUES). One might want to add functions to control this possible explosion of values: NONE (takes arbitrary number of args, no results), INITIAL (takes non-zero number of args, returns first), FINAL (non-zero number of args, returns last). This approach has the possible disadvantage that it is no longer possible to define functions that return ``possibly useful'' results that will be quietly discarded if used in a no-value context; again, experience in Pop suggests that this is not a problem. [Why did I say Pop's multiple values are unsuitable for Scheme? Because they are implemented by simply using a stack, and making it visible to the programmer. There is *no* special checking for number-of-arguments-passed or number-of-results-returned; if F is defined as, say, add-1, then (F 1 2) returns multiple values, viz, (1 3). Conversely, if G expects two arguments, and delivers (eg) their sum, then (F 2 (G 3)) delivers 6. The surprising thing is that this free-wheeling approach to arity results in few errors, most of which are detected very quickly (stack underflow is, of course, checked for). However, I do not see this as a sensible change of approach for Scheme; it would make more sense in this context to keep arity-checking for calls.] -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
mkatz@garlic.stanford.EDU (Morris Katz) (04/06/91)
Date: Mon, 1 Apr 1991 18:16:33 GMT From: Doug Moen <snitor!doug@bloom-beacon.mit.edu> 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 ((<formals> <actuals>)) <body>) is now equivalent to (apply (lambda <formals> <body>) <actuals>) 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 --------------------
oz@gpu.utcs.utoronto.ca (04/06/91)
[from oz@nexus.yorku.ca] An article of mine [responding to jinx, about his arm needing twisting] that I thought was cancelled seem to have made it out for some reason, please ignore it. I later re-worded the article to be more thoughtful and less confrontational, and have received meaningful responses to it. sorry for any misunderstanding. oz --- Ps: Here is a mistake I was powerless to correct ;-) --- What ought to disturb us are not mistakes | Internet: oz@nexus.yorku.ca in general, but only those of them that we | Uucp: utzoo/utai!yunexus!oz are powerless to correct. -- David Miller | Phone: 1+416-736-5257-33976