December 02, 2003
We should crawl before we walk or run.  I think a nice start would be to implement multiple return values.

(a, b, c) = foo(p1, p2, p3) ;

1) Rules for passing parameters remain the same.

2) Functions may not be over loaded via their return parameter lists.

3) Define parameterized lvalues and rvalues.

    i.e. (a, b, c) = (e+f, z*u, n*m) ; /* valid statement */
    i.e. (x, y) = (r*cos(theta), r*sin(theta)) ; /* valid statement */
    i.e. (x, y) = toCartesian( r, theta) ;    /* toCartesian returns a
parmeterized rvalue */

4) Multiple return values are passed back via registers and stack in a well-defined way.

5) Single output functions use the current calling conventions.

6) Multiple return value functions cannot be linked by another language. The call is D to D.

Mark.

"Georg Wrede" <Georg_member@pathlink.com> wrote

>
> Functions returning several values could be used like
>
> (a,b,c) = func(d,e,f,g);
>
> Using this we could make the code clearer and easier
> to understand if we decided that (d,e,f,g) could only
> be in-parameters, and that the only way to get out-
> functionality is via return values (e.g. (a,b,c)).
>
Nice, but not necessary.

> This would make the code quite readable. But the cost,
> and the pre-requisite, would be to skip out-parameters
> entirely from the language.
>
Not necessary.  This would certainly break existing code and also break C backward compatability (I think).

> Why? Well, if we are browsing code written by others,
> then we either can rely on the right side being input-
> only, or then we can't. Only if we can do that, the
> code becomes readily browsable for us.
>
> This brings up the question, should we do this?
>
> I for one would think that this is a truly interesting
> aspect, and it may contain quite some promises. OTOH,
> this would be such a profound change in the language
> that we may risk alienating C(++), Java, and other
> users. So, this had better be worth the cost.

I do not think this would be a profound change.

>
> Also, if we went for this kind of strategy, then why
> not make both the in- (the right side) and the out-
> (the left side) streamable! That is, a function
> could take any number of parameters, and it could
> also return an arbitrary number of parameters!
> Well, such languages exist already. APL I think
> is one. But from what I've had to program with
> APL, this sure doesn't contribute to the overall
> readability when it comes right down to it.
>
This would a lot all at once.

> Then there is another solution. A company using D
> could internally decide that every function takes
> one in-parameter and one out-parameter. They would
> be structs. This would of course lead to assignments
> both at the input and the output, but since Good
> Code has short functions, most of the locals could
> be in structs to begin with. Standardising these
> structs could result in increased clarity and maybe
> even more overall structure in the program.
>
> And we could skip a profound remake of D.
>
>

>


December 02, 2003
"Berin Loritsch" <bloritsch@d-haven.org> wrote

>
> ... Runs away screamming ... NOOOOOO!!!!!
>

It is OK, you can run back, please.


> When the only difference between two functions is the return parameter,
you are
> inviting disaster.  How can you guarantee in what context which of the two methods are called?

I agree.
>
> I personally would not want to maintain an application which uses such a construct, and it is explicitly forbidden in other languages.  I think for
a
> good reasons.
>
So do I.

> It's a stretch for me just to accept multiple return values--when you
throw in
> differing methods based on return type, things get really messy really
fast.
> Please don't do such a thing.
>

Three messages back in the thread you asked about how  to ignore a value in the return parameters.  The body of my reply enumerates some ideas that I had regarding ignoring return values, one of which was overloading on the return list.  I think, if multiple return values were implemented, we should start with the simplest case (fixed return parameter lists) and let the laguage grow from there.  The proper direction will not be known until people try the simple case.



December 02, 2003
"brad beveridge" <brad@nospam.com> wrote in message news:bqguk7$31jb$1@digitaldaemon.com...
> How would multiple returns work if you don't provide places for the
> returns to go?
> ie
> (int, int) = someFunc(int);

A call would look like this.

type1 foo ;
type2 bar ;

(foo, bar) = someFunc(param1, param2, /* etc*/) ;

>
> (a) = someFunc(10);
>
> Further to that, how would nested functions work?
> (int) = someFunc(int, int, int)
> (int, int, int) = foo(float)
>
> (a) = someFunc(foo(10.0));

Nesting for the time being should not be allowed.  The function should return a parameterized rvalue ready for assignment to a suitable lvalue.  If a compelling case arises this could be explored.

>
> What about determining which function to call based on the return type?
> (int) = foo(int)
> (int, int) = foo(int)
>
> (a,b) = foo(10);
> c = foo(5);
>

Probably not a good place to start.

> I don't know how complex that is starting to get for the compiler.  I like the idea of mulitple returns, it can be very handy - but I get the feeling that it is probably quite hard to implement in a compiler.
>
> Brad
>
> > "Ben Hinkle" <bhinkle4@juno.com> wrote
> >
> >
> >>MATLAB allows both a variable number of inputs and outputs. It does this
> >
> > by
> >
> >>reserving the parameter names "varargin" and "varargout" to have special meaning (they collect all the remaining inputs or outputs into a list).
It
> >>also defines the variables "nargin" and "nargout" in the function body
to
> >
> > be
> >
> >>the number of inputs and outputs.
> >>Multiple outputs are used very often in MATLAB code. varargout is used
> >
> > much
> >
> >>less frequently, and most uses probably are to implement generic
function
> >>evaluators.
> >>
> >>-Ben
> >>
> >>
> >
> >
> > Yes, but
> >
> > Matlab != D
> >
> > Matlab is interpreted and is therefore slow.  Don't get me wrong, I love Matlab, but when speed is necessary, a compiled, optimized language is necesary.  For me, I like C, I avoid C++ and D is appealing.  Since D is still being developed, multiple returns seem like a nice addition.
> >
> > Mark.
> >
> >
>


December 02, 2003
Yeah, maybe the multiple outputs would be a nice feature. However, the issue of
left-side parameters is a little special: they cannot bear an useful information
for the function or they can? Usually when you pass func(&x) you may also give x
an useful value.
But, for programming simplicity, it would be a great enhancement.


In article <bqfq35$1br4$1@digitaldaemon.com>, Mark J. Brudnak says...
>
>
>"Berin Loritsch" <bloritsch@d-haven.org> wrote in message
>>
>> Honestly, I have not even thought about a feature like this...
>Practlically
>> speaking, how would you call such a beast in the client code?
>>
>> (foo, bar) = calculateIt( x, y, z, t );
>>
>> and if we wanted to explicitly ignore a value:
>>
>> (,bar) = calculateIt( x, y, z, t );
>>
>> ???
>>
>
>Perhaps, but presumably, you could overload a function based on its return parameter list as well as its argument list.  For example
>
>class MyClass {
>    int func( int x, int y, int z) {
>        int result ;
>        /* do stuff */
>        return result ;
>    }
>    (int, float) func( int x, int y, int z) {
>        float bar ;
>        int ibar = func(x, y, z) ;
>        /* do stuff */
>        return ( ibar, bar ) ;
>    }
>}
>
>Ignoring return parameters may be something necessary however.  I would say that they are analogous to default arguments (like C++).  Of course it would be difficult to determine which return value one may wish to ignore,  so requiring that all ignorable return parameters appear at the end of the parameter list is a bit restrictive.
>
>An alternative syntax for ignoring return parameters would be using the null keyword (is 'null' a key word in D?).  For example,
>
>MyClass me = new MyClass ;
>int ibar;
>float bar ;
>(ibar, null) = me.func(1.0, 2.0, 3.0) ;
>/* or */
>(null, bar) = me.func(1.0, 2.0, 3.0) ;
>/* or */
>(ibar, bar) = me.func(1.0, 2.0, 3.0) ;
>
>Another alternative is to use named parameters.  (I saw some discussion
>about this.)  For example
>
>class MyClass {
>    (int a, float b) func( int x, int y, int z) {
>        float bar ;
>        int ibar  ;
>        /* do stuff */
>        return ( ibar, bar ) ;
>    }
>}
>
>MyClass me = new MyClass ;
>int ibar;
>float bar ;
>(a:ibar) = me.func(1.0, 2.0, 3.0) ;
>/* or */
>(b:bar) = me.func(1.0, 2.0, 3.0) ;
>/* or */
>(a:ibar, b:bar) = me.func(1.0, 2.0, 3.0) ;
>/* or just */
>(ibar, bar) = me.func(1.0, 2.0, 3.0) ;
>
>This whole discussion however raises a larger issue.  That is, what we are really discussing is expanding and generalizing what an lvalue and rvalue can and should be.  For example if an lvalue was allowed to take the form of a parameter list, then the multiple return would be almost free.  For example, a parameter list lvalue (and rvalue) would result in statements such as:
>
>(x, y) = (r*cos(theta), r*sin(theta)) ;
>
>/* which is more compact than.. */
>x = r*cos(theta) ;
>y = r*sin(theta)  ;
>
>which would function equivalent to the following function call,
>
>(x, y) = toCartesian( r, theta) ;
>
>/* which is more elegant (IMO) than  */
>
>toCartesian( r, theta, &x, &y) ;
>
>Mark.
>
>
>


December 02, 2003
Some other issues on the multiple params return:

- the values to ignore may be (,,usefulone,,,anotheruseful)=functie(etc1,etc2);
-however, you will not be able anymore to call functie1(functie2(etc1,etc2)) and
this will be a little clumsy:
(v1,v2)=functie2(...);
functie1(v1);

But is something to think more seriously of it




In article <bqfr31$1d2p$1@digitaldaemon.com>, Berin Loritsch says...
>
>Mark J. Brudnak wrote:
>
>> "Berin Loritsch" <bloritsch@d-haven.org> wrote in message
>> 
>>>Honestly, I have not even thought about a feature like this...
>> 
>> Practlically
>> 
>>>speaking, how would you call such a beast in the client code?
>>>
>>>(foo, bar) = calculateIt( x, y, z, t );
>>>
>>>and if we wanted to explicitly ignore a value:
>>>
>>>(,bar) = calculateIt( x, y, z, t );
>>>
>>>???
>>>
>> 
>> 
>> Perhaps, but presumably, you could overload a function based on its return parameter list as well as its argument list.  For example
>> 
>> class MyClass {
>>     int func( int x, int y, int z) {
>>         int result ;
>>         /* do stuff */
>>         return result ;
>>     }
>>     (int, float) func( int x, int y, int z) {
>>         float bar ;
>>         int ibar = func(x, y, z) ;
>>         /* do stuff */
>>         return ( ibar, bar ) ;
>>     }
>> }
>> 
>
>... Runs away screamming ... NOOOOOO!!!!!
>
>When the only difference between two functions is the return parameter, you are inviting disaster.  How can you guarantee in what context which of the two methods are called?
>
>I personally would not want to maintain an application which uses such a construct, and it is explicitly forbidden in other languages.  I think for a good reasons.
>
>It's a stretch for me just to accept multiple return values--when you throw in differing methods based on return type, things get really messy really fast. Please don't do such a thing.
>


December 02, 2003
Agreed.  However, please don't make it look like an overloaded method that only
differs by return type.  That would be an error IMNSHO.

Mark Brudnak wrote:
> "Berin Loritsch" <bloritsch@d-haven.org> wrote
> 
> 
>>... Runs away screamming ... NOOOOOO!!!!!
>>
> 
> 
> It is OK, you can run back, please.
> 
> 
> 
>>When the only difference between two functions is the return parameter,
> 
> you are
> 
>>inviting disaster.  How can you guarantee in what context which of the two
>>methods are called?
> 
> 
> I agree.
> 
>>I personally would not want to maintain an application which uses such a
>>construct, and it is explicitly forbidden in other languages.  I think for
> 
> a
> 
>>good reasons.
>>
> 
> So do I.
> 
> 
>>It's a stretch for me just to accept multiple return values--when you
> 
> throw in
> 
>>differing methods based on return type, things get really messy really
> 
> fast.
> 
>>Please don't do such a thing.
>>
> 
> 
> Three messages back in the thread you asked about how  to ignore a value in
> the return parameters.  The body of my reply enumerates some ideas that I
> had regarding ignoring return values, one of which was overloading on the
> return list.  I think, if multiple return values were implemented, we should
> start with the simplest case (fixed return parameter lists) and let the
> laguage grow from there.  The proper direction will not be known until
> people try the simple case.
> 
> 
> 

December 02, 2003
Hello Brad,

> How would multiple returns work if you don't provide places for the returns to go?
> ie
> (int, int) = someFunc(int);
> 
> (a) = someFunc(10);

Two possibilities:
1) Compiler error - all parameters are mandatory

2) When there are fewer LHS variables, they are assigned starting from the first one, if types match. Harder for everyone, though.

> Further to that, how would nested functions work?
> (int) = someFunc(int, int, int)
> (int, int, int) = foo(float)
> 
> (a) = someFunc(foo(10.0));

One useful feature of tuples in Python is automatic packing and unpacking.

(int, float) Func(); // Or just "int, float Func()"?
and call it as:
int i; float f;

i, f = Func();

With the compiler automatically managing the packing into a tuple.

Your "nested functions" (I would think they are an entirely different beast!) could benefit from automatic unpacking as well.

(a) = someFunc(foo(10.0));

This would translate into
a = someFunc(a, b, c);

where a,b,c forms the tuple (a, b, c) returned by foo().

> What about determining which function to call based on the return type?
> (int) = foo(int)
> (int, int) = foo(int)
> 
> (a,b) = foo(10);
> c = foo(5);

In this case there is no ambiguity. In fact, these are unrelated issues -  returning multiple values and overloading on return types. Actually if you have the former, there is less incentive to have the latter.

The whole issue of overloading by return type is probably centered around the fact that in most languages one can ignore the return type. I am told Ada supports overloading on return types and return values can never be ignored. Anyone with good Ada experience would like to comment?

> I don't know how complex that is starting to get for the compiler.  I like the idea of mulitple returns, it can be very handy - but I get the feeling that it is probably quite hard to implement in a compiler.

I remember seeing a discussion on the web (couple of years back?) by the designers of C, C++ and Java. Asked what they would change if they were to do it all over again, James Gosling cited multiple return values as a major one (Ha, I found an archived link - http://www.gotw.ca/publications/c_family_interview.htm).

Of course, I am not advocating anything here. Multiple return values is one feature, I think, that can't be added as an afterthought. I mean, in a seamless manner. I think that may be the reason Java still doesn't have it. All of the things discussed in this thread exist in various other languages. But of course, that's not enough of a reason to put them all into D.

Cheers,
Sarat Venugopal

December 03, 2003
In article <bqgvbc$14a$1@digitaldaemon.com>, Mark Brudnak wrote:
> We should crawl before we walk or run.  I think a nice start would be to implement multiple return values.
> 
> (a, b, c) = foo(p1, p2, p3) ;
> 
> 1) Rules for passing parameters remain the same.
> 
> 3) Define parameterized lvalues and rvalues.
> 
>     i.e. (a, b, c) = (e+f, z*u, n*m) ; /* valid statement */
>     i.e. (x, y) = (r*cos(theta), r*sin(theta)) ; /* valid statement */
>     i.e. (x, y) = toCartesian( r, theta) ;    /* toCartesian returns a
> parmeterized rvalue */

I must ask, how would a statement like the second one be implemented? Maybe like this:

x = r*cos(theta);
y = r*sin(theta);

Or perhaps like this:

__temp1 = r*cos(theta); __temp2 = r*sin(theta);
x = __temp1; y = __temp2;

The latter would be less straightforward but would save the user from
the bug of trying to swap variables with "(x, y) = (y, x);"

> 4) Multiple return values are passed back via registers and stack in a well-defined way.

The same way that structs are returned, I presume.

Another question: could the grammar be changed so that tuples would not need the enclosing ()'s, just like in someone's python example?

Which is more readable, more maintainable, more comfortable?

Alternative 1:

    int x;
    int y;
    (int, int) f(int, int);

    (x, y) = f(1, 2);

Alternative 2:

    int x;
    int y;
    int, int f(int, int);

    x, y = f(1, 2);

Yet another question: Is the following possible?

    int x, y;

and what does it mean? Does it declare two integers, or is it half tuple, half declaration? Should I be able to write this:

    int x;
    x, int y = f(1, 2); // or maybe:
    (x, int y) = f(1, 2);

Does this complicate parsing?

>> This would make the code quite readable. But the cost,
>> and the pre-requisite, would be to skip out-parameters
>> entirely from the language.
>>
> Not necessary.  This would certainly break existing code and also break C backward compatability (I think).

You most certainly do not mean this but something else, because the C language does not have out parameters. It would break compatibility, at least in spirit, with languages and conventions which use "in" and "out" declarations; IDL comes to mind.

Finally, I agree that both multiple return values and tuples should be part of any modern programming language, and that they are much cleaner alternative than "out parameters" which is a contradiction in terms (not to mention that one man's in is another man's out).

But it is also true that they don't come for free. First, the sequencing operator must be ditched and a proper workaround found. Then, the grammar must be revised to see whether the language can consume such a pervasive change (as the comma is used everywhere in language where lists exist, I expect that there may rise conflicts between the customary usages and tuple usage). Finally, the semantics of the tuples must be worked out with the proper care and attention so that will fit in the language as if originally designed for it, that they won't create any pitfalls, and that they won't break (too many) existing idioms.

-Antti

December 05, 2003
"Antti Sykäri" <jsykari@gamma.hut.fi> wrote

<snip>

>
> I must ask, how would a statement like the second one be implemented? Maybe like this:
>
> x = r*cos(theta);
> y = r*sin(theta);
>
> Or perhaps like this:
>
> __temp1 = r*cos(theta); __temp2 = r*sin(theta);
> x = __temp1; y = __temp2;
>
> The latter would be less straightforward but would save the user from
> the bug of trying to swap variables with "(x, y) = (y, x);"
>

This would be legal, because the tuple-operator (,,,,,) would have *higher* precidence than the assignment operator.  More importantly, each item in the RHS tuple would be evaluated.

i.e.

(re, im) = (exp(re)*cos(im), exp(re)*sin(im)) ;  /* complex exponentiation
*/

Here exp(re) would be evaluated twice.

> > 4) Multiple return values are passed back via registers and stack in a well-defined way.
>
> The same way that structs are returned, I presume.
>

Yes, exactly.

> Another question: could the grammar be changed so that tuples would not need the enclosing ()'s, just like in someone's python example?
>
> Which is more readable, more maintainable, more comfortable?
>
> Alternative 1:
>
>     int x;
>     int y;
>     (int, int) f(int, int);
>
>     (x, y) = f(1, 2);
>

Yes, very clear.


> Alternative 2:
>
>     int x;
>     int y;
>     int, int f(int, int);
>
>     x, y = f(1, 2);
>

No, it is not symmetric.

> Yet another question: Is the following possible?
>
>     int x, y;
>
> and what does it mean? Does it declare two integers, or is it half tuple, half declaration? Should I be able to write this:
>
>     int x;
>     x, int y = f(1, 2); // or maybe:

No, it is not clear you have a tuple on the LHS.

>     (x, int y) = f(1, 2);

Does D allow this in other areas?  It is probably best to follow the D rules.  Is it legal to write:

int y = foo(1,2) ;

<snip>


December 07, 2003
With structs but you have the advantage of selecting whatever value you want from a returned struct.

With multiple returns you have to consider all of the return values.
There is a slight advantage to a packaged multiple return.  This allows
you to package a set of values for return and keep the syntax simpler.  You can
select out of the return bundle whatever value you want.

The Java designers were very oversighted in removing structs.  Without structs and without multiple returns there is no way to include or substitute multiple returns.  So structs are useful without multiple return.

In article <bqgbtf$2726$1@digitaldaemon.com>, Mark J. Brudnak says...
>
>
>"Makaan Kublai Kahn" <Makaan_member@pathlink.com> wrote
>
>>
>> You can already do this.  You can return a struct I in C and C++.
>> A struct can contain more than one value and encapsulate more than one
>return
>> of types.  It is better to return a struct of values than have a multiple
>return
>> for functions.  This is how it was intended to return multiple types.
>>
>
>Yes, but why should you have to define a structure just because you have to return two or three values rather than one?  It is more code to write.  The compiler knows all of the relevent information to define the structure implicitly.  In other words the compiler is responsible for generating the struct.  For example.
>
>FOR THE CALLER SIDE
>===================
>D code as written:
>
>    int x ;
>    float y ;
>     (x, y) = func(10, 3.14159, "foo") ;
>
>The compiler then compiles it as if I had written:
>
>    int x ;
>    float y ;
>    { /* compiler generated block statement */
>        struct tempStruct {
>            int var1 ;
>            float var2 ;
>        }
>        tempStruct temp ;
>        temp = func(10, 3.14159, "foo") ;
>        x = temp.var1 ;
>        y = temp.var2 ;
>    }
>
>FOR THE CALLEE SIDE
>===================
>D code as written:
>
>     (int , float) = func(int a, float b, char [] c) {
>        int result1 ;
>        float result2 ;
>
>        /* do stuff */
>
>        return( result1, result2 ) ;
>    }
>
>The compiler then compiles it as if I had written:
>
>     (int , float) = func(int a, float b, char [] c) {
>        int result1 ;
>        float result2 ;
>
>        /* do stuff */
>
>         { /* compiler generated block statement */
>            struct tempStruct {
>                int var1 ;
>                float var2 ;
>            }
>            tempStruct temp ;
>            temp.var1 = result1 ;
>            temp.var2 = result2 ;
>            return temp ;
>        }/* end compiler generated block statement */
>    }
>
>This is also what happens to the aruments as well.  They are stuffed into an implicitly defined structure consisting of either the registers and/or the stack in a well defined way.  It is only natural to do the same thing in reverse.  (i.e. use the registers and stack to pass back multiple return values in a well defined way.  In this case our ficticious compiler used a struct, in an actual implementation it would use some registers and then the stack.)
>
>mark.
>
>> A link for gcc is:  http://gcc.gnu.org/ml/gcc/1998-11/msg01087.html
>>
>> Here's an example from that link of returning a struct:
>>
>> ================================================================== -- z1.c --------------------------------------------------------
>>
>> struct s {
>> int x;
>> };
>>
>> struct s func (int y)
>> {
>> struct s xx;
>> xx.x = y * 10;
>> return xx;
>> }
>>
>> -- z2.c --------------------------------------------------------
>>
>> struct s {
>> int x;
>> };
>>
>> extern struct s func (int y);
>>
>> void func2 (struct s x1, struct s x2)
>> {
>> printf ("%d %d\n", x1.x, x2.x);
>> }
>>
>>
>> int main ()
>> {
>> func2 (func (1), func (2));
>> }
>> ----------------------------------------------------------------
>>
>>
>> On linux, compile z1.c with gcc 2.7.2 and z2.c with egcs and link them together.  The resulting program prints
>>
>> 10 1
>>
>> rather than the expected
>>
>> 10 20
>>
>> ==========================================================
>>
>>
>>
>>
>
>