October 15, 2009
On Thu, 15 Oct 2009 02:58:51 -0400, Don <nospam@nospam.com> wrote:

> Andrei Alexandrescu wrote:
>> Also, the much-discussed identity:
>>  x @= y    <-->    x = x @ y
>>  is difficult to enforce statically in practice. I think some types would want to define both to achieve good efficiency. It would be hard for the compiler to render one unnecessary or to prove that the two are equivalent.
>
> Yes, it could not be enforced. But note that there would be no ambiguity as to which should be used in any given expression.
> I would propose that the opXXXAssign() variants should exist *only* for performance optimisation, and be completely divorced from the "+=" syntax (effectively, += would be discarded after the parsing step).
> My ancient Bugzilla proposal actually included opSubAssign() and opSubAssign_r() for  x = x - y; and x = y - x;
> If   the x @= y    <-->    x = x @ y transformations became legal, this would allow unnecessary temporaries to be completely eliminated.
>
> The suggested transformation would be that x = x + y would be transformed into x.opAddAssign(y) whenever it exists, and x = y + x would become x.opAddAssign_r(y)
> The transformations would therefore be entirely predictable.

Oh, I didn't realize that's what you meant.  I thought that opXxxAssign was to be eliminated and x += y was to be transformed into x.opAssign(x.opXxx(y).  I like this proposal better -- opXxxAssign can exist for optimization reasons, and enforcing the relationship between @= and = @ by parsing one into the other.

By parsing x += y into x = x + y, and allowing overloading of a chain of operations, you may even get more mileage out of something like x += y + z + w;

Someone earlier suggested opXxx(a1, a2, ...) could be interpreted as an operator for dealing with chained operations.  You could also maybe have an opChain or something that takes as arguments the operands and the operators to maybe perform some optimization (i.e. like reordering matrix operations).

You should update your DIP to specify that opXxxAssign should be allowed for optimization purposes (BTW, classes could benefit from this, because then x += y *would* be the same as x = x + y).

-Steve
October 15, 2009
On Thu, 15 Oct 2009 04:48:57 -0400, Fawzi Mohamed <fmohamed@mac.com> wrote:

> On 2009-10-14 23:09:26 +0200, "Robert Jacques" <sandford@jhu.edu> said:
>
>> On Wed, 14 Oct 2009 16:49:28 -0400, Andrei Alexandrescu  <SeeWebsiteForEmail@erdani.org> wrote:
>>
>>> Jason House wrote:
>>>> Bill Baxter Wrote:
>>>>
>>>>> On Wed, Oct 14, 2009 at 7:42 AM, Jason House
>>>>> <jason.james.house@gmail.com> wrote:
>>>>>> Andrei Alexandrescu Wrote:
>>>>>>
>>>>>>> Right now we're in trouble with operators: opIndex and opIndexAssign
>>>>>>> don't seem to be up to snuff because they don't catch operations like
>>>>>>>  a[b] += c;
>>>>>>>  with reasonable expressiveness and efficiency.
>>>>>> I would hope that *= += /= and friends could all be handled  efficiently with one function written by the programmer. As I see it,  there are 3 basic steps:
>>>>>> 1. Look up a value by index
>>>>>> 2. Mutate the value
>>>>>> 3. Store the result
>>>>> And as Chad J reminds us, same goes for in-place property mutations
>>>>> like  a.b += c.
>>>>> It's just a matter of  accessing  .b  vs .opIndex(b).   And really
>>>>> same goes for any function  a.memfun(b) += c could benefit from the
>>>>> same thing (a.length(index)+=3 anyone?)
>>>>>
>>>>>> it's possible to use opIndex for #1 and opIndexAssign for #3, but  that's not efficient. #1 and #3 should be part of the same function,  but I think #2 shouldnot be. What about defining an opIndexOpOpAssign  that accepts a delegate for #2 and then use compiler magic to  specialize/inline it?
>>>>> It could also be done using a template thing to inject the "mutate the
>>>>> value" operation:
>>>>  The only issue with templates is that they're never virtual
>>>  You can make virtuals out of templates, but not templates out of  virtuals. I think Walter is now inclined to look at a template-based  solution for operator overloading. That would save a mighty lot of code  without preventing classes that prefer virtual dispatch from doing so.
>>>  Andrei
>>  I've done something similar for a SmallVec struct. Most of the operator  overloads are actually aliases of templated functions (one each for  uni-ops, bi-ops, bi-op_r and opassign)
>
> I would really like a solution to all the overloading ops, as I missed them in NArray, I think that some small rewriting is ok, but it must be *small*, no magic as already said by other numerics can be tricky.
> Also Andrei proposal seem workable, but there is also another solution:
>
> Note that a ref return for opIndex, could work in most situations.
> As Bill correctly pointed out sparse matrix offer the most challenging example, there one wants to have two different functions: opIndex and opIndexLhs, the second being called when the index is on the left hand side of an assignment, so that reading a 0 entry in a matrix returns 0, whereas assigning it allocates place for it.
> This makes it slightly more complex to control what is being assigned (as you need to return a structure overloading opXAssign, but I think it would be ok in most cases.
>
> Fawzi
>

Would you like some example code?
October 15, 2009
On 2009-10-15 17:51:56 +0200, "Robert Jacques" <sandford@jhu.edu> said:

> On Thu, 15 Oct 2009 04:48:57 -0400, Fawzi Mohamed <fmohamed@mac.com> wrote:
> 
>> [...]
>> Note that a ref return for opIndex, could work in most situations.
>> As Bill correctly pointed out sparse matrix offer the most challenging  example, there one wants to have two different functions: opIndex and  opIndexLhs, the second being called when the index is on the left hand  side of an assignment, so that reading a 0 entry in a matrix returns 0,  whereas assigning it allocates place for it.
>> This makes it slightly more complex to control what is being assigned  (as you need to return a structure overloading opXAssign, but I think it  would be ok in most cases.
>> 
>> Fawzi
>> 
> 
> Would you like some example code?

I suppose you would like it ;)

// example 1
class Matrix(T){
 T opIndex(size_t i,size_t j){
   if (has_(i,j)){
     return data[index(i,j)];
   } else {
     return cast(T)0;
   }
 }
 ref T opIndexLhs(size_t i,size_t j){
   if (has_(i,j)){
     return &data[index(i,j)];
   } else {
     //alloc new place and set things so that index(i,j) returns it
     return &data[index(i,j)];
   }
 }
}

then
m[3,4]+=4.0;
would be converted in
m.opIndexLhs(3,4)+=4.0;

typically with just one method (opIndexLhs) all += -=,... are covered

if one needs more control

class AbsMatrix(T){
 T opIndex(size_t i,size_t j){
   if (has_(i,j)){
     return data[index(i,j)];
   } else {
     return cast(T)0;
   }
 }
 struct Setter{
   T* pos;
    void opAddAssign(T val){
      *pos+=abs(val);
    }
 }
 Setter opIndexLhs(size_t i,size_t j){
   Setter pos;
   if (has_(i,j)){
      res.pos=&data[index(i,j)];
   } else {
     //alloc new place and set things so that index(i,j) returns it
     res.pos=&data[index(i,j)];
   }
   return res;
 }
}

if one does not allow ref T as return type then one can return a pointer and do
static if(is(typeof(*m.opIndexLhs(3,4))))
	*m.opIndexLhs(3,4)+=4.0;
else
	m.opIndexLhs(3,4)+=4.0;

so that the trick with the struct is still possible.

October 15, 2009
On 2009-10-15 22:55:02 +0200, Fawzi Mohamed <fmohamed@mac.com> said:

> On 2009-10-15 17:51:56 +0200, "Robert Jacques" <sandford@jhu.edu> said:
> 
>> On Thu, 15 Oct 2009 04:48:57 -0400, Fawzi Mohamed <fmohamed@mac.com> wrote:
>> 
>>> [...]
>>> Note that a ref return for opIndex, could work in most situations.
>>> As Bill correctly pointed out sparse matrix offer the most challenging  example, there one wants to have two different functions: opIndex and  opIndexLhs, the second being called when the index is on the left hand  side of an assignment, so that reading a 0 entry in a matrix returns 0,  whereas assigning it allocates place for it.
>>> This makes it slightly more complex to control what is being assigned  (as you need to return a structure overloading opXAssign, but I think it  would be ok in most cases.
>>> 
>>> Fawzi
>>> 
>> 
>> Would you like some example code?
> 
> I suppose you would like it ;)
> 
> // example 1
> class Matrix(T){
>   T opIndex(size_t i,size_t j){
>     if (has_(i,j)){
>       return data[index(i,j)];
>     } else {
>       return cast(T)0;
>     }
>   }
>   ref T opIndexLhs(size_t i,size_t j){
>     if (has_(i,j)){
>       return &data[index(i,j)];
>     } else {
>       //alloc new place and set things so that index(i,j) returns it
>       return &data[index(i,j)];
>     }
>   }
> }

mmmh I mixed up a bit the ref returning and pointer returning case...
clearly there should be no &...

> 
> then
> m[3,4]+=4.0;
> would be converted in
> m.opIndexLhs(3,4)+=4.0;
> 
> typically with just one method (opIndexLhs) all += -=,... are covered
> 
> if one needs more control
> 
> class AbsMatrix(T){
>   T opIndex(size_t i,size_t j){
>     if (has_(i,j)){
>       return data[index(i,j)];
>     } else {
>       return cast(T)0;
>     }
>   }
>   struct Setter{
>     T* pos;
>      void opAddAssign(T val){
>        *pos+=abs(val);
>      }
>   }
>   Setter opIndexLhs(size_t i,size_t j){
>     Setter pos;
>     if (has_(i,j)){
>        res.pos=&data[index(i,j)];
>     } else {
>       //alloc new place and set things so that index(i,j) returns it
>       res.pos=&data[index(i,j)];
>     }
>     return res;
>   }
> }
> 
> if one does not allow ref T as return type then one can return a pointer and do
> static if(is(typeof(*m.opIndexLhs(3,4))))
> 	*m.opIndexLhs(3,4)+=4.0;
> else
> 	m.opIndexLhs(3,4)+=4.0;
> 
> so that the trick with the struct is still possible.


1 2 3 4
Next ›   Last »