December 15, 2006
Walter Bright wrote:
> Stewart Gordon wrote:
>> C'mon, what's your use case for being allowed to return something other than the new value of a from the expression (a = b)?
> 
> I don't have one. But I prefer to impose as few restrictions as possible, as people keep finding unanticipated cool new things to do. So I'd argue that there should be compelling case put forward to impose such a restriction, rather than wondering what one can do with the flexibility.

We already have what strikes the rest of us as a compelling case: what we've just told you that returning from opAssign _should_ be able to do.

Stewart.
December 15, 2006
== Quote from Andrei Alexandrescu (See Website for Email) > Now consider:
> struct S {
>    int a;
>    S opAssign(int x) {
>      a = x;
>      return this;
>    }
> }
> void Increment(inout S x) {
>    ++x.a;
> }
> S a;
> Increment(a = 5);
> This is going to have very different (and useless and unwanted) semantics.
> Again, the right thing to do: give the Caesar what belongs to the
> Caesar. Have the user do the assignment (and return void), and have the
> compiler pass the lhs lvalue around, when needed.
> Andrei

This reminds me of the "opIndex" discussion we had here quite a while ago.  The problem was that for user defined containers, you can't simulate X[i] if the contained type is something like a struct.

class C {
   S s1;

   S opIndex(int i)
   {
      return s1;
   }
}

There is an opIndexAssign(), but it has the same problem when applied to the inout
parameter.

From the discussion at the time (Ben Hinkle might remember) I recall two possibilities that seemed to make sense to me.

1. Add a return type qualifier with "inout" semantics, like a C++ "&" type.

    Something like one of these, inout of course looks a bit funny:

    ref S opIndexRef(int i);
    inout S opIndexRef(int i);

2. Return S* and let the compiler apply the "*".

    S* opIndex(int i);
    Compiler silently transforms foo(x[i]) into foo(*x[i])

I think #2 is a lot like what has been suggested for opAssign():

A* A::opAssign(inout A b);
S* A::opIndexAssign(int i);

assign1: a.opAssign(b)      -> (a.opAssign(b), a)
assign2: a.opAssign(b)      -> *(a.opAssign(b))
index1:  a.opIndexAssign(i) -> *opIndexAssign(i)

Where index1 is my opIndex suggestion (from way back) and assign1 and assign2 are
Walter and Andrei's possible syntaxes for opAssign() respectively.

Conceptually, the problem looks similar to me: how to "tunnel" an assignable type through a return value of a method, like the C++ "&" types do.  The opIndex version needs to tunnel up through more layers than opAssign() does since it's not the A type, so the (a.opIndex(b), a) can't work there of course.

I would vote for "S*" being the conceptual return type in both cases for
opAssign() and opIndex(), although in the case of opAssign() it wouldn't bother me
if the user visible signature was "void opAssign(...)" and the compiler handles
the pointer as it does with this().

Kevin
December 15, 2006
Kevin Bealer wrote:
> This reminds me of the "opIndex" discussion we had here quite a while ago.  The
> problem was that for user defined containers, you can't simulate X[i] if the
> contained type is something like a struct.
> 
> class C {
>    S s1;
> 
>    S opIndex(int i)
>    {
>       return s1;
>    }
> }
> 
> There is an opIndexAssign(), but it has the same problem when applied to the inout
> parameter.

Hm. This inability to return lvalues looks like a serious loophole
within D's type system.

> 1. Add a return type qualifier with "inout" semantics, like a C++ "&" type.
> 
>     Something like one of these, inout of course looks a bit funny:
> 
>     ref S opIndexRef(int i);
>     inout S opIndexRef(int i);

This will have serious ripples through the type system. For example, people might start to ask themselves whether they can define standalone inout variables, and what that means. Or, tell inside a template whether it received an inout parameter or not.

At any rate, Perl 5 has implemented this exact hack, and seems to be
working with it. See:

http://search.cpan.org/~nwclark/perl-5.8.7/pod/perlsub.pod#Lvalue_subroutines

The Perl guys seem to be unhappy about it because they labeled it as "experimental" in Perl 5 and they discuss eliminating it in Perl 6, in favor of some even more exotic features. See:

http://dev.perl.org/perl6/rfc/149.html

> 2. Return S* and let the compiler apply the "*".
> 
>     S* opIndex(int i);
>     Compiler silently transforms foo(x[i]) into foo(*x[i])

This is also (probably more) unsatisfactory. People will be able to
index and obtain lvalues, but will be unable to return something
assignable from a function:

++s[a]; // possible through a compiler hack
++s(a, b); // impossible

> Conceptually, the problem looks similar to me: how to "tunnel" an assignable type
> through a return value of a method, like the C++ "&" types do.  The opIndex
> version needs to tunnel up through more layers than opAssign() does since it's not
> the A type, so the (a.opIndex(b), a) can't work there of course.
> 
> I would vote for "S*" being the conceptual return type in both cases for
> opAssign() and opIndex(), although in the case of opAssign() it wouldn't bother me
> if the user visible signature was "void opAssign(...)" and the compiler handles
> the pointer as it does with this().

I see this as D's #1 major problem as of today. I think the function
returning inout is better, but I foresee a bunch of issues with it. The
question whether lvalues are needed beyond function return values must
be satisfactorily answered.


Andrei

December 15, 2006
Stewart Gordon wrote:
> We already have what strikes the rest of us as a compelling case: what we've just told you that returning from opAssign _should_ be able to do.

That case was trying to make opAssign be a constructor. Since there are other ways to construct it, I don't see why it's a compelling case. What is bought with this that cannot be otherwise done?
December 15, 2006
Chris Nicholson-Sauls wrote:
> Lionello Lunesu wrote:
>> "Alexander Panek" <a.panek@brainsware.org> wrote in message news:eln5td$2pta$1@digitaldaemon.com...
>>> Are you, actually, talking about a D -> MSIL compiler, so it can be run inside the VM and a .NET implementation in D, for that purpose? Or do you mean something /similar/ to C# + .NET? If so, I agree. Something like that has to be in D. :) I hope this will pop up
>>
>> We had one, once : ( but then the guy's HDD crashed... Sad, sad story.
>>
>> L.
>>
> 
> https://mywebspace.wisc.edu/daaugustine/web/d/
> 
> This is all that remains of all his work, so far as I can tell.  Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again.  Oh well.
> 
> -- Chris Nicholson-Sauls

I think I have pretty much all of the binary files he released (though they're archived on some CD in my closet), but he never released any source before his hard drive crashed, so I don't have any of that. And I think the binaries targeted some beta version of .NET and required .dll's that you'd only have if you installed a beta version of Visual Studio.

Seemed like a cool idea, but it took a lot of effort just to try it out...

-- 
jcc7
December 15, 2006
Andrei Alexandrescu (See Website For Email) wrote:
> I see this as D's #1 major problem as of today. I think the function
> returning inout is better, but I foresee a bunch of issues with it. The
> question whether lvalues are needed beyond function return values must
> be satisfactorily answered.

Ok, I think I understand the issue now. Also, it only applies to struct types, not class types. Class types are already reference types, so for them it's a non-issue.
December 15, 2006
Andrei Alexandrescu (See Website for Email) wrote:
> Lionello Lunesu wrote:
>> Walter Bright wrote:
>>> It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to:
>>>
>>>     *(a.opAssign(b))
>>>
>>
>> class C {
>>   C* opAssign(...) {...}
>> }
>> struct S {
>>   S* opAssign(...) {...}
>> }
>>
>> Seriously?? Ugh.
>>
>> Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized.
>>
>> Am I missing something?
> 
> There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious.

I'd think that the optimizer would not only eliminate the extra copy, but the entire construction of the return value .. No?

L.
December 15, 2006
== Quote from Andrei Alexandrescu (See Website For Email)
(SeeWebsiteForEmail@erdani.org)'s article
> Kevin Bealer wrote:
...
> > 2. Return S* and let the compiler apply the "*".
> >
> >     S* opIndex(int i);
> >     Compiler silently transforms foo(x[i]) into foo(*x[i])
> This is also (probably more) unsatisfactory. People will be able to index and obtain lvalues, but will be unable to return something assignable from a function:
>
> ++s[a]; // possible through a compiler hack
> ++s(a, b); // impossible

I missed this possibility.  In order to solve just this next step { ++s(a, b); }, it seems like D would need something that copies like a pointer to either a struct or class reference, but automatically turns into (or acts like) a regular reference or LValue when something like ++ is applied.  This means something in between a pointer and an LValue.

I'll keep calling it 'ref' for now.  I think to get the semantics we'd expect it needs these properties:

1. For struct, it can be returned from a method/function or passed as an (inout) argument one or more times without causing any struct-copy to happen, and still referring to the original class reference (not just the original class).

2. For class, it can be used to modify the original object reference itself, i.e. for:

ref C X::opIndex(int i);
foo(inout C);
foo(x[i]) should have the potential to modify the reference in the i'th location
of x, by replacing with a different C object.

3. It should autoconvert to (or behave like) a regular LValue when something like ++ or += happens.  Of course if += is called and opAddAssign() returns "ref T", then the returned reference would propagate a ref to the same original entity.

4. Due to #3, it cannot support things like pointer arithmetic (which is perfectly okay).  Or assignment to the ref itself (the assign and other operations would by applied to the referred object of course).

It's also probably convertable to a pointer via "&", i.e. address-of returns a pointer-to-original instead of pointer-to-ref.  I guess this means it's impossible to get an address of the "ref" type itself except via trickery?


I kind of hate to say it, but given all this, how far am I from describing a C++ "&" type?  Is there anything the C++ type does that isn't in the above list? (Other than the new proposal for '& &' references I guess, which seems unnecessary for D.)

Kevin
December 15, 2006
Lionello Lunesu wrote:
> Andrei Alexandrescu (See Website for Email) wrote:
>> Lionello Lunesu wrote:
>>> Walter Bright wrote:
>>>> It is possible to force the return type of opAssign. But I'd suggest making it an S*, with the rewrite to:
>>>>
>>>>     *(a.opAssign(b))
>>>>
>>>
>>> class C {
>>>   C* opAssign(...) {...}
>>> }
>>> struct S {
>>>   S* opAssign(...) {...}
>>> }
>>>
>>> Seriously?? Ugh.
>>>
>>> Why not just "C opAssign" and "S opAssign"? In the opCall discussion you said yourself that the return value will be optimized.
>>>
>>> Am I missing something?
>>
>> There is no *extra* copying, in the sense that exactly one copy is done. But most of the time the copy obtained is not used, in which case that one copy is spurious.
> 
> I'd think that the optimizer would not only eliminate the extra copy, but the entire construction of the return value .. No?

If the routine is inlined perhaps, but otherwise not.


Sean
December 15, 2006
Justin C Calvarese wrote:
> Chris Nicholson-Sauls wrote:
>> Lionello Lunesu wrote:
>>> "Alexander Panek" <a.panek@brainsware.org> wrote in message news:eln5td$2pta$1@digitaldaemon.com...
>>>> Are you, actually, talking about a D -> MSIL compiler, so it can be run inside the VM and a .NET implementation in D, for that purpose? Or do you mean something /similar/ to C# + .NET? If so, I agree. Something like that has to be in D. :) I hope this will pop up
>>>
>>> We had one, once : ( but then the guy's HDD crashed... Sad, sad story.
>>>
>>> L.
>>>
>>
>> https://mywebspace.wisc.edu/daaugustine/web/d/
>>
>> This is all that remains of all his work, so far as I can tell.  Some day it would be nice if http://www.scratch-ware.net/dfiles/ suddenly existed again.  Oh well.
>>
>> -- Chris Nicholson-Sauls
> 
> I think I have pretty much all of the binary files he released (though they're archived on some CD in my closet), but he never released any source before his hard drive crashed, so I don't have any of that. And I think the binaries targeted some beta version of .NET and required .dll's that you'd only have if you installed a beta version of Visual Studio.

Can't assemblies be fairly easily disassembled?

L.