November 19, 2009
Rainer Deyke wrote:
> Andrei Alexandrescu wrote:
>> I am thinking that representing operators by their exact token
>> representation is a principled approach because it allows for
>> unambiguous mapping, testing with if and static if, and also allows
>> saving source code by using only one string mixin. It would take more
>> than just a statement that it's hackish to convince me it's hackish. I
>> currently don't see the hackishness of the approach, and I consider it a
>> vast improvement over the current state of affairs.
> 
> Isn't opBinary just a reduced-functionality version of opUnknownMethod
> (or whatever that is/was going to be called)?
> 
> T opBinary(string op)(T rhs) {
>     static if (op == "+") return data + rhs.data;
>     else static if (op == "-") return data - rhs.data;
>     ...
>     else static assert(0, "Operator "~op~" not implemented");
> }
> 
> T opUnknownMethod(string op)(T rhs) {
>     static if (op == "opAdd") return data + rhs.data;
>     else static if (op == "opSub") return data - rhs.data;
>     ...
>     else static assert(0, "Method "~op~" not implemented");
> }
> 
> I'd much rather have opUnknownMethod than opBinary.  If if I have
> opUnknownMethod, then opBinary becomes redundant.
> 
> 

Passing op as the symbol allows for mixin("this.data"~op~"that.data;");

What I was hoping for was a catch-all for unknown non-operation methods, which could allow for dispatching to functions that are not even known at runtime (ie. trigger a lookup in a shared object, or pass along to a scripting language engine).

November 19, 2009
Travis Boucher wrote:
> Rainer Deyke wrote:
>> Andrei Alexandrescu wrote:
>>> I am thinking that representing operators by their exact token
>>> representation is a principled approach because it allows for
>>> unambiguous mapping, testing with if and static if, and also allows
>>> saving source code by using only one string mixin. It would take more
>>> than just a statement that it's hackish to convince me it's hackish. I
>>> currently don't see the hackishness of the approach, and I consider it a
>>> vast improvement over the current state of affairs.
>>
>> Isn't opBinary just a reduced-functionality version of opUnknownMethod
>> (or whatever that is/was going to be called)?
>>
>> T opBinary(string op)(T rhs) {
>>     static if (op == "+") return data + rhs.data;
>>     else static if (op == "-") return data - rhs.data;
>>     ...
>>     else static assert(0, "Operator "~op~" not implemented");
>> }
>>
>> T opUnknownMethod(string op)(T rhs) {
>>     static if (op == "opAdd") return data + rhs.data;
>>     else static if (op == "opSub") return data - rhs.data;
>>     ...
>>     else static assert(0, "Method "~op~" not implemented");
>> }
>>
>> I'd much rather have opUnknownMethod than opBinary.  If if I have
>> opUnknownMethod, then opBinary becomes redundant.
>>
>>
> 
> Passing op as the symbol allows for mixin("this.data"~op~"that.data;");

Also it does not require remembering the correspondence between symbols and their names.

> What I was hoping for was a catch-all for unknown non-operation methods, which could allow for dispatching to functions that are not even known at runtime (ie. trigger a lookup in a shared object, or pass along to a scripting language engine).
> 

I agree. This is something very interesting.


Andrei
November 19, 2009
bearophile wrote:
> Andrei Alexandrescu:
> 
>> * Encode operators by compile-time strings. For example, instead of the plethora of opAdd, opMul, ..., we'd have this:
>>
>> T opBinary(string op)(T rhs) { ... }
>>
>> The string is "+", "*", etc.
> 
> Can you show an example of defining an operator, like a minus, with that?
> 
> In my set data structure I'd like to define "<=" among two sets as "is subset". Can that design allow me to overload just <= and >= ? (opCmp is not enough here).
> 
> Bye,
> bearophile

And what about the unary operators. Sorry I was not following the opBinary thread from the beginning, so it might have been discussed before. If opBinary has a syntax like this, unary operators require something like this at least just for completeness.
(And maybe trinary operators ?: ) :)
Can they still be overloaded ? Will they have a similar syntax ? If so
what about the e++ and ++e operators? How they are distinct ? Or is the latter eliminated from the language ?
Though, I like the idea, just a quick look at a code and you can see all the operators in a place.

Bye, Gzp
November 19, 2009
Justin Johansson wrote:
>> 6. There must be many things I forgot to mention, or that cause grief to many of us. Please add to/comment on this list.
>>
> 
> Sorry I'm not familiar with any prior discussion on this or even where it's at (basically I'm in D1 world), but what about
> 
> constructors for structs

That's been in D2 for a long time. :)

http://www.digitalmars.com/d/2.0/struct.html#Struct-Constructor

-Lars
November 19, 2009
Gzp wrote:
> bearophile wrote:
>> Andrei Alexandrescu:
>>
>>> * Encode operators by compile-time strings. For example, instead of the plethora of opAdd, opMul, ..., we'd have this:
>>>
>>> T opBinary(string op)(T rhs) { ... }
>>>
>>> The string is "+", "*", etc.
>>
>> Can you show an example of defining an operator, like a minus, with that?
>>
>> In my set data structure I'd like to define "<=" among two sets as "is subset". Can that design allow me to overload just <= and >= ? (opCmp is not enough here).
>>
>> Bye,
>> bearophile
> 
> And what about the unary operators. Sorry I was not following the opBinary thread from the beginning, so it might have been discussed before. If opBinary has a syntax like this, unary operators require something like this at least just for completeness.

Yes; opBinary was just given as an example. Unary operators look like this:

T opUnary(string op)();

> (And maybe trinary operators ?: ) :)
> Can they still be overloaded ? Will they have a similar syntax ? If so
> what about the e++ and ++e operators? How they are distinct ? Or is the latter eliminated from the language ?
> Though, I like the idea, just a quick look at a code and you can see all the operators in a place.
> 
> Bye, Gzp

I think user code should only define ++e. Then the compiler can use that to define e++.


Andrei
November 19, 2009
Wed, 18 Nov 2009 18:35:18 -0800, Andrei Alexandrescu wrote:

> grauzone wrote:
>> Andrei Alexandrescu wrote:
>>> The rewrite is done long after lexing, so no low-level problems there.
>> 
>> Oh, I thought it would let you introduce new operators. But it's only about the existing ones.
>> 
>> I find the idea to identify the operator using a string very sloppy and sillyl just like using string mixins for small delegates in std.algorithm etc.; but you'd probably say "it works and is useful" and "it's short" and "it solves the current problem", so... whatever.
> 
> We're trying to improve on the current situation, which forces the user to manually define a lot of small functions. If you have convincing reasons to argue that the current state of affairs is actually better, I'm all ears - both Walter and I could use less work, particularly if the outcome sucks (see e.g. T[new]). Also, if you have ideas on how things could be done in a way that you'd find not sloppy and not silly, that would be even better.

Does the new system allow overriding only some binary operations and not all of them at once? I thought generic member functions were non-virtual?
November 19, 2009
Lars T. Kyllingstad wrote:
> Justin Johansson wrote:
>>> 6. There must be many things I forgot to mention, or that cause grief to many of us. Please add to/comment on this list.
>>>
>>
>> Sorry I'm not familiar with any prior discussion on this or even where it's at (basically I'm in D1 world), but what about
>>
>> constructors for structs
> 
> That's been in D2 for a long time. :)
> 
> http://www.digitalmars.com/d/2.0/struct.html#Struct-Constructor
> 
> -Lars

Thanks Lars.  Brilliant.  Guess I should have done my homework.
November 19, 2009
retard <re@tard.com.invalid> wrote:

> Does the new system allow overriding only some binary operations and not
> all of them at once?

struct S {
    S opBinary( string op )( S rhs ) if ( op == "+" ) {
        // Do stuff
    }
}

> I thought generic member functions were non-virtual?


Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> 3. The template functions can't be virtual (fixable with a forwarding thunk written once and for all).

-- 
Simen
November 19, 2009
Thu, 19 Nov 2009 09:33:07 +0100, Simen kjaeraas wrote:

> retard <re@tard.com.invalid> wrote:
> 
>> Does the new system allow overriding only some binary operations and not all of them at once?
> 
> struct S {
>      S opBinary( string op )( S rhs ) if ( op == "+" ) {
>          // Do stuff
>      }
> }
> 
>> I thought generic member functions were non-virtual?

I meant this:

class foo {
  int value;

  this(int a) { value = a; }

  foo opAdd(foo other) { return new foo(value + other.value); }
}

class bar : foo {
  int value2;

  this(int a, int b) { super(a); value2 = b; }

  override foo opAdd(foo other) { return new bar(value + other.value,
value2+1); }
}

> 
> 
> Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
>> 3. The template functions can't be virtual (fixable with a forwarding thunk written once and for all).

November 19, 2009
Andrei Alexandrescu wrote:
> We're entering the finale of D2 and I want to keep a short list of things that must be done and integrated in the release. It is clearly understood by all of us that there are many things that could and probably should be done.
> 
> 1. Currently Walter and Don are diligently fixing the problems marked on the current manuscript.
> 
> 2. User-defined operators must be revamped.

Should opIndex and opSlice be merged?
This would be simpler, and would allow multi-dimensional slicing.
Probably the simplest way to do this would be to use fixed length arrays  of length 2 for slices.
So, for example, if the indices are integers, then
opIndex(int x) { } is the 1-D index, and
opIndex(int[2] x) {} is a slice from x[0] to x[1],
which exactly corresponding to the current opSlice(x[0]..x[1]).

Since fixed-length arrays are now passed by value (yay!) this would be just as efficient as the current method. It'd be simple to implement.

Downsides: (1) Arguably not terribly intuitive.
(2) This would not let you have a slicing from A..A, while simultaneously allowing indexing by A[2]. But I don't think that makes sense anyway -- what would $ return? (Note that it doesn't stop you from having indexes of type A[2], and slice from A[2]..A[2], which might be important for containers of 2D vectors. It's only the case where indexing and slicing are different types, which is prohibited).

This is the only solution I have which doesn't introducing more coupling between the language and standard library.
Another possible solution is to define a special Slice struct in std.object; downside is that it creates yet another pseudo-keyword.

A third solution would be to use tuples;  currently I think it's impossible, but if tuple auto-flattening is removed there may be a chance. Even so, it might be a little clumsy to use.

A fourth solution is to retain the status quo, and not provide the multi-dimensional slicing feature <g>. This is actually about the same amount of work as option (1), since opDollar would need to work for opSlice (I only implemented it for opIndex).