www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Operator overhaul

reply "Bill Baxter" <wbaxter gmail.com> writes:
(Reply to message on d.announce)

On Wed, Oct 22, 2008 at 9:36 AM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 Jason House wrote:

 Is there a good reason why it shouldn't be possible to use opAssign as
 a replacement for opIndexAssign?

 --bb

opindexAssign will still be needed when opindex has a non-ref return type.

I think the entire operator paraphernalia is due for a serious overhaul.

O_o! Oh noohs! We may have to start calling it the "E" language soon. Seriously, though, if the overhaul makes it possible to do multiple slices with multiple $'s, then I'm all for it. As in hyperArray[$-1, 1..$, 0..$-2] Even better would be python's "just leave it out" slicing: As in hyperArray[$-1, 1.., ..$-2] It's a bit easier to read in my eyes. There was also the stuff discussed before about passing in the op as a delegate or template parameter to give the class designer a chance to run some pre- or post- op code as desired. Or rewriting x+=y type ops to be x=x+y So we have: 1. multiple slice indexing 2. generalized $ indexing 3. generalized op execution Any other operator-related issues? --bb
Oct 21 2008
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Bill Baxter wrote:
 (Reply to message on d.announce)
 
 On Wed, Oct 22, 2008 at 9:36 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 Jason House wrote:

 Is there a good reason why it shouldn't be possible to use opAssign as
 a replacement for opIndexAssign?

 --bb



O_o! Oh noohs! We may have to start calling it the "E" language soon. Seriously, though, if the overhaul makes it possible to do multiple slices with multiple $'s, then I'm all for it. As in hyperArray[$-1, 1..$, 0..$-2] Even better would be python's "just leave it out" slicing: As in hyperArray[$-1, 1.., ..$-2] It's a bit easier to read in my eyes. There was also the stuff discussed before about passing in the op as a delegate or template parameter to give the class designer a chance to run some pre- or post- op code as desired. Or rewriting x+=y type ops to be x=x+y So we have: 1. multiple slice indexing 2. generalized $ indexing 3. generalized op execution Any other operator-related issues?

There would be composition (of the kind usually addressed through expression templates), but we don't have a good solution to that yet. Andrei
Oct 21 2008
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:
 There would be composition (of the kind usually addressed through 
 expression templates), but we don't have a good solution to that yet.

You really want to make D an awesome language :-) I though that in D the expression templates stuff was left to be done by the AST macros, but if they don't come until D3, then a more specialized functionality may be used in the meantime... (if necessary even to be later removed in D3). Bye, bearophile
Oct 21 2008
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
bearophile wrote:
 Andrei Alexandrescu:
 There would be composition (of the kind usually addressed through 
 expression templates), but we don't have a good solution to that
 yet.

You really want to make D an awesome language :-) I though that in D the expression templates stuff was left to be done by the AST macros, but if they don't come until D3, then a more specialized functionality may be used in the meantime... (if necessary even to be later removed in D3).

I agree. Anyhow, the floor is open for suggestions on how to overhaul the entire operator business. I'll tell you what is, to me, clearly dead wrong: whenever one implements any class that wants to overload a bunch of operators (number-like classes etc.) inevitably it boils down to repetitive code like this: struct Whatever { Whatever opAdd(Whatever rhs) { return member + rhs.member; } Whatever opSubtract(Whatever rhs) { return member - rhs.member; } ... } and lines and lines of brain damaged repetition, repetition, repetition. If we add opWhateverAssign that will get even worse. The challenge is, then, to define a way to overload operators that makes the classic applications of the feature short and sweet. Andrei
Oct 21 2008
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:
 struct Whatever
 {
      Whatever opAdd(Whatever rhs) { return member + rhs.member; }
      Whatever opSubtract(Whatever rhs) { return member - rhs.member; }
      ...
 }
 and lines and lines of brain damaged repetition, repetition, repetition. 
 If we add opWhateverAssign that will get even worse. The challenge is, 
 then, to define a way to overload operators that makes the classic 
 applications of the feature short and sweet.

I'm a newbie still regarding such stuff. At a first sight a possible solution is to resort to mathematics: state somehow that class has the property of being a Number, maybe Additive too, then such methods become automatically defined for free... :-) I think functional languages have similar things, it may be named structural subtyping... I am too much ignorant about this topics, I am sorry. Once I know Haskell well enough I'll be able to talk about this stuff much better. I suggest you to ask this question in the "Lambda the Ultimate" blog, they are quite expert of such matters, and they are very willing to help. If you say them you want to make D "more functional" they will work for you in the nights too for free :-) At the moment D1 has class mixins to do something like that, they can find the name of the class by themselves too. Bye, bearophile
Oct 21 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
bearophile wrote:
 Andrei Alexandrescu:
 struct Whatever { Whatever opAdd(Whatever rhs) { return member +
 rhs.member; } Whatever opSubtract(Whatever rhs) { return member -
 rhs.member; } ... } and lines and lines of brain damaged
 repetition, repetition, repetition. If we add opWhateverAssign that
 will get even worse. The challenge is, then, to define a way to
 overload operators that makes the classic applications of the
 feature short and sweet.

I'm a newbie still regarding such stuff. At a first sight a possible solution is to resort to mathematics: state somehow that class has the property of being a Number, maybe Additive too, then such methods become automatically defined for free... :-) I think functional languages have similar things, it may be named structural subtyping... I am too much ignorant about this topics, I am sorry. Once I know Haskell well enough I'll be able to talk about this stuff much better.

Well it's great you are improving on that kind of knowledge. It's also very nice that you acknowledge lack of expertise in the matter (something very rare on the Usenet and in this group too) and finally it is nice that in the same post you _do_ give advice, which suggests there may be a contradiction somewhere :o).
 I suggest you to ask this question in the "Lambda the Ultimate" blog,
 they are quite expert of such matters, and they are very willing to
 help. If you say them you want to make D "more functional" they will
 work for you in the nights too for free :-)
 
 At the moment D1 has class mixins to do something like that, they can
 find the name of the class by themselves too.
 
 Bye, bearophile

Our problem is not that we're out of ideas, but instead that there's an embarrassment of riches in terms of the particular solution to adopt. LtU is an excellent forum populated by many Illuminati, yet sometimes discussions could get tedious because they become overly general. For example: "I want to do this for D." "Wait, why doesn't D make everything a reference? That would certainly make things cleaner" etc. Andrei
Oct 21 2008
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2008-10-21 22:24:08 -0400, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 I agree. Anyhow, the floor is open for suggestions on how to overhaul 
 the entire operator business. I'll tell you what is, to me, clearly 
 dead wrong: whenever one implements any class that wants to overload a 
 bunch of operators (number-like classes etc.) inevitably it boils down 
 to repetitive code like this:
 
 struct Whatever
 {
      Whatever opAdd(Whatever rhs) { return member + rhs.member; }
      Whatever opSubtract(Whatever rhs) { return member - rhs.member; }
      ...
 }
 
 and lines and lines of brain damaged repetition, repetition, 
 repetition. If we add opWhateverAssign that will get even worse. The 
 challenge is, then, to define a way to overload operators that makes 
 the classic applications of the feature short and sweet.

Not terribly difficult. Add the op keyword, which could work both as a function name and an operator substitute inside the operator function: struct Whatever { Whatever op(+,-,/,*)(Whatever rhs) { member op rhs.member; return this; } auto op(=,+=,-=,/=,*=)(Whatever rhs) { member op rhs.member; return member; } auto op([])(int index, T value) { return member[index]; } // This one is a template: auto op([]=,[]+=,[]-=,[]/=,[]*=)(T)(int index, T value) { return member[index] op value; } } The sad thing is that then we lose the meaningful names, so perhaps this would be more in order: struct Whatever { Whatever opAdd,opSub,opDiv,opMul(Whatever rhs) { op(member, rhs.member); return this; } auto opAssign,opAddAssign,opSubAssign,opDivAssign,opMulAssign(Whatever rhs) { op(member, rhs.member); return member; } auto opIndex(int index, T value) { return member[index]; } // This one is a template: auto opIndexAssign,opIndexAddAssign,opIndexSubAssign,opIndexDivAssign,opI dexMulAssign(T)(int index, T value) { return op(member[index], value); } } Hum, no, sadly this one doesn't cut it. :-/ -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Oct 21 2008
prev sibling next sibling parent BCS <ao pathlink.com> writes:
Reply to Andrei,

 bearophile wrote:
 
 Andrei Alexandrescu:
 
 There would be composition (of the kind usually addressed through
 expression templates), but we don't have a good solution to that
 yet.
 

I though that in D the expression templates stuff was left to be done by the AST macros, but if they don't come until D3, then a more specialized functionality may be used in the meantime... (if necessary even to be later removed in D3).

the entire operator business. I'll tell you what is, to me, clearly dead wrong: whenever one implements any class that wants to overload a bunch of operators (number-like classes etc.) inevitably it boils down to repetitive code like this: struct Whatever { Whatever opAdd(Whatever rhs) { return member + rhs.member; } Whatever opSubtract(Whatever rhs) { return member - rhs.member; } ... } and lines and lines of brain damaged repetition, repetition, repetition. If we add opWhateverAssign that will get even worse. The challenge is, then, to define a way to overload operators that makes the classic applications of the feature short and sweet. Andrei

I want to do: typedef real MyReal(int i) { MyReal!(i) opAdd(MyReal!(i)); //default impl } If left without a body this would /only/ redefine the allowed operators and the types they result in. The actual code gen would be exactly the same. Allowing bodies might be cool.
Oct 21 2008
prev sibling next sibling parent Christian Kamm <kamm-incasoftware removethis.de> writes:
Andrei Alexandrescu wrote:
 I'll tell you what is, to me, clearly dead
 wrong: whenever one implements any class that wants to overload a bunch
 of operators (number-like classes etc.) inevitably it boils down to
 repetitive code like this:
 
 struct Whatever
 {
      Whatever opAdd(Whatever rhs) { return member + rhs.member; }
      Whatever opSubtract(Whatever rhs) { return member - rhs.member; }
      ...
 }
 
 and lines and lines of brain damaged repetition, repetition, repetition.
 If we add opWhateverAssign that will get even worse. The challenge is,
 then, to define a way to overload operators that makes the classic
 applications of the feature short and sweet.

Maybe that could be done similarly to boost.operators (http://www.boost.org/doc/libs/1_36_0/libs/utility/operators.htm), only using template mixins? The main problem I see with any solution is that it will need to be pretty complicated to accomodate for all cases and be generally useful. In the end, understanding what the mixins do exactly may take longer than having a set of repetitive, but trivial overloads.
Oct 21 2008
prev sibling parent Don <nospam nospam.com.au> writes:
Andrei Alexandrescu wrote:
 bearophile wrote:
 Andrei Alexandrescu:
 There would be composition (of the kind usually addressed through 
 expression templates), but we don't have a good solution to that
 yet.

You really want to make D an awesome language :-) I though that in D the expression templates stuff was left to be done by the AST macros, but if they don't come until D3, then a more specialized functionality may be used in the meantime... (if necessary even to be later removed in D3).

I agree. Anyhow, the floor is open for suggestions on how to overhaul the entire operator business. I'll tell you what is, to me, clearly dead wrong: whenever one implements any class that wants to overload a bunch of operators (number-like classes etc.) inevitably it boils down to repetitive code like this: struct Whatever { Whatever opAdd(Whatever rhs) { return member + rhs.member; } Whatever opSubtract(Whatever rhs) { return member - rhs.member; } ... } and lines and lines of brain damaged repetition, repetition, repetition. If we add opWhateverAssign that will get even worse. The challenge is, then, to define a way to overload operators that makes the classic applications of the feature short and sweet.

My earlier contribution is in bugzilla 124, which would eliminate all unnecessary temporaries, though it doesn't address the repetitive code issue (in fact it makes the repitition slightly worse). It seems clear that we need to restrict some of the useless freedoms that exist at present: (1) a OP= b _must_ mean a = a OP b (allowing the compiler to convert from one to the other); (2) The return value of x OP= y must be x. Beyond this it's not quite so obvious. I wish we could get rid of opPostInc/Dec, with their ridiculous semantics. (Convert it to preInc, generate an error if the return value is used).
Oct 22 2008
prev sibling parent reply BCS <ao pathlink.com> writes:
Reply to Andrei,


 There would be composition (of the kind usually addressed through
 expression templates), but we don't have a good solution to that yet.
 

what is that? Link?
Oct 21 2008
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
BCS wrote:
 Reply to Andrei,
 
 
 There would be composition (of the kind usually addressed through
 expression templates), but we don't have a good solution to that yet.

what is that? Link?

The classic introduction: http://ubiety.uwaterloo.ca/~tveldhui/papers/Expression-Templates/exprtmpl.html And another one: http://www.angelikalanger.com/Articles/Cuj/ExpressionTemplates/ExpressionTemplates.htm Googling for <<expression templates c++>> yields some more. Andrei
Oct 21 2008
prev sibling next sibling parent reply Russell Lewis <webmaster villagersonline.com> writes:
Bill Baxter wrote:
 (Reply to message on d.announce)
 
 On Wed, Oct 22, 2008 at 9:36 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 Jason House wrote:

 Is there a good reason why it shouldn't be possible to use opAssign as
 a replacement for opIndexAssign?

 --bb



O_o! Oh noohs! We may have to start calling it the "E" language soon. Seriously, though, if the overhaul makes it possible to do multiple slices with multiple $'s, then I'm all for it. As in hyperArray[$-1, 1..$, 0..$-2] Even better would be python's "just leave it out" slicing: As in hyperArray[$-1, 1.., ..$-2] It's a bit easier to read in my eyes. There was also the stuff discussed before about passing in the op as a delegate or template parameter to give the class designer a chance to run some pre- or post- op code as desired. Or rewriting x+=y type ops to be x=x+y So we have: 1. multiple slice indexing 2. generalized $ indexing 3. generalized op execution Any other operator-related issues?

While we're at it, we need to think about lazy slicing (with $). Say that you have an lazy array type (where it only generates the members when required). In that sort of situation, you don't necessarily want to generate the entire array (so that you can calculate the length) the first time that somebody does a = a[0..$-1]; or tmp = a[$-1]; You want to be able to keep a reference that says "so many characters from the end of the array, though the length of the array isn't known yet."
Oct 22 2008
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Russell Lewis:
 While we're at it, we need to think about lazy slicing (with $).

If I understand what you are talking about, then you may want to look at islice() of Python and xslice() of my libs. You can give a lazy sequence and specify the slicing: xslice(someiterable, 10, 20) => computes the first 20 items, ignores the first 10, and returns an array of the second 10. Adding a syntax to D to do that, similar to the array slicing seems like a nice thing. Bye, bearophile
Oct 23 2008
prev sibling parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Wed, 22 Oct 2008 21:22:09 -0700,
Russell Lewis wrote:
 Any other operator-related issues?

While we're at it, we need to think about lazy slicing (with $). Say that you have an lazy array type (where it only generates the members when required). In that sort of situation, you don't necessarily want to generate the entire array (so that you can calculate the length) the first time that somebody does a = a[0..$-1]; or tmp = a[$-1]; You want to be able to keep a reference that says "so many characters from the end of the array, though the length of the array isn't known yet."

If you have a lazy array, its opSlice(size_t, size_t) can return another lazy array, properly configured. What's the problem?
Oct 23 2008
parent Russell Lewis <webmaster villagersonline.com> writes:
Sergey Gromov wrote:
 Wed, 22 Oct 2008 21:22:09 -0700,
 Russell Lewis wrote:
 Any other operator-related issues?

that you have an lazy array type (where it only generates the members when required). In that sort of situation, you don't necessarily want to generate the entire array (so that you can calculate the length) the first time that somebody does a = a[0..$-1]; or tmp = a[$-1]; You want to be able to keep a reference that says "so many characters from the end of the array, though the length of the array isn't known yet."

If you have a lazy array, its opSlice(size_t, size_t) can return another lazy array, properly configured. What's the problem?

The problem is that when you have an opSlice which isn't $-aware, the code a = a[0..$-1]; gets converted by the compiler into a = a[0..a.length-1]; which, of course, means that our array isn't lazy anymore. Ideally, opSlice should have variants which allow the data structure to say "I will remember to never return you the last few elements" without having to calculate the array length at slice-time.
Oct 23 2008
prev sibling next sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Bill Baxter wrote:
 (Reply to message on d.announce)
 
 On Wed, Oct 22, 2008 at 9:36 AM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 Jason House wrote:

 Is there a good reason why it shouldn't be possible to use opAssign as
 a replacement for opIndexAssign?

 --bb



O_o! Oh noohs! We may have to start calling it the "E" language soon. Seriously, though, if the overhaul makes it possible to do multiple slices with multiple $'s, then I'm all for it. As in hyperArray[$-1, 1..$, 0..$-2] Even better would be python's "just leave it out" slicing: As in hyperArray[$-1, 1.., ..$-2] It's a bit easier to read in my eyes. There was also the stuff discussed before about passing in the op as a delegate or template parameter to give the class designer a chance to run some pre- or post- op code as desired. Or rewriting x+=y type ops to be x=x+y So we have: 1. multiple slice indexing 2. generalized $ indexing 3. generalized op execution Any other operator-related issues? --bb

I can never remember how the values for comparison work. An enum somewhere (except that's a new type, so just constants): struct Comparison { const int LeftIsGreater = -1; const int Equal = 0; const int RightIsGreater = 1; } That's simple, it doesn't require any compiler changes, and it'll save me some headaches.
Nov 01 2008
parent reply "Stewart Gordon" <smjg_1998 yahoo.com> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:gei556$1sgc$3 digitalmars.com...
<snip>
 I can never remember how the values for comparison work. An enum somewhere 
 (except that's a new type, so just constants):

 struct Comparison
 {
 const int LeftIsGreater = -1;
 const int Equal = 0;
 const int RightIsGreater = 1;
 }

 That's simple, it doesn't require any compiler changes, and it'll save me 
 some headaches.

Enums are implicitly convertible to their underlying types, so there's no point in using a struct instead. If you're taking advantage of the ability to return an arbitrary int for performance, are you planning to multiply by one of these constants? Stewart. -- My e-mail address is valid but not my primary mailbox. Please keep replies on the 'group where everybody may benefit.
Nov 01 2008
next sibling parent Christopher Wright <dhasenan gmail.com> writes:
Stewart Gordon wrote:
 "Christopher Wright" <dhasenan gmail.com> wrote in message 
 news:gei556$1sgc$3 digitalmars.com...
 <snip>
 I can never remember how the values for comparison work. An enum 
 somewhere (except that's a new type, so just constants):

 struct Comparison
 {
 const int LeftIsGreater = -1;
 const int Equal = 0;
 const int RightIsGreater = 1;
 }

 That's simple, it doesn't require any compiler changes, and it'll save 
 me some headaches.

Enums are implicitly convertible to their underlying types, so there's no point in using a struct instead.

I didn't realize that.
 If you're taking advantage of the ability to return an arbitrary int for 
 performance, are you planning to multiply by one of these constants?

This would mainly be for writing opCmp, and as documentation. As an alternative: struct Comparison { static Comparison LeftIsGreater = {-1}; static Comparison Equal = {0}; static Comparison RightIsGreater = {1}; int value; bool opEquals(int i) { if (value == 0 && i == 0) return true; if (value < 0 && i < 0) return true; if (value > 0 && i > 0) return true; return false; } }
Nov 01 2008
prev sibling parent "Stewart Gordon" <smjg_1998 yahoo.com> writes:
"Denis Koroskin" <2korden gmail.com> wrote in message 
news:op.ujyj0qpio7cclz proton.creatstudio.intranet...
<snip>
 I often use the following pattern:

 [code]
     auto possibleResults[3] = [
         Result1,
         Result2,
         Result3,
     ];

     int cmp = compare(a, b) + 1; // cmp is 0, 1 or 2
     return possibleResults[cmp];
 [/code]

So you rely on custom-made compare functions that always return -1, 0 or 1? In this case, why not make them return 0, 1 or 2 in the first place? Stewart. -- My e-mail address is valid but not my primary mailbox. Please keep replies on the 'group where everybody may benefit.
Nov 01 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 01 Nov 2008 23:42:30 +0300, Stewart Gordon <smjg_1998 yahoo.com>  
wrote:

 "Christopher Wright" <dhasenan gmail.com> wrote in message  
 news:gei556$1sgc$3 digitalmars.com...
 <snip>
 I can never remember how the values for comparison work. An enum  
 somewhere (except that's a new type, so just constants):

 struct Comparison
 {
 const int LeftIsGreater = -1;
 const int Equal = 0;
 const int RightIsGreater = 1;
 }

 That's simple, it doesn't require any compiler changes, and it'll save  
 me some headaches.

Enums are implicitly convertible to their underlying types, so there's no point in using a struct instead. If you're taking advantage of the ability to return an arbitrary int for performance, are you planning to multiply by one of these constants? Stewart.

I often use the following pattern: [code] auto possibleResults[3] = [ Result1, Result2, Result3, ]; int cmp = compare(a, b) + 1; // cmp is 0, 1 or 2 return possibleResults[cmp]; [/code] instead of bunch of ifs.
Nov 01 2008