November 19, 2009
On Thu, 19 Nov 2009 11:53:51 +0300, Don <nospam@nospam.com> wrote:

> 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).

I'd like for a..b to be auto-magically rewritten into range(a, b). This way you can distinguish between indexing and slicing:

struct Range
{
   T lower, upper;
   // front, back, popFront, popBack, empty
}

Range!(T) range(T)(T lower, T upper) {
   return Range!(T)(lower, upper);
}

auto opIndex(Range!(int) range) { // corresponds to foo[a..b];
}

auto opIndex(int index1, int index2) {  // corresponds to foo[a, b];
}

This will also get rid of a special case inside foreach:

foreach (i; a..b) {
}

since a..b will be converted into a range, generic rules would apply to it.
November 19, 2009
> 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

Thanks, that's great.

Gzp
November 19, 2009
Don wrote:
> 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.

Yes, please. :)


> 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.
> [...]

I think this is the only way to do it, and I don't see why it's less intuitive than anything else. If it helps, you can always define

  alias size_t[2] slice_t;

(or something similar) in object.d.

  // Submatrix slice
  real opIndex(slice_t rows, slice_t cols) { ... }

Doesn't look bad at all.

-Lars
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. Fortunately Don already put in an important piece of functionality (opDollar). What we're looking at is a two-pronged attack motivated by Don's proposal:
> 
> http://prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP7
> 
> The two prongs are:
> 
> * 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. We need to design what happens with read-modify-write operators like "+=" (should they be dispatch to a different function? etc.) and also what happens with index-and-modify operators like "[]=", "[]+=" etc. Should we go with proxies? Absorb them in opBinary? Define another dedicated method? etc.
> 
> * Loop fusion that generalizes array-wise operations. This idea of Walter is, I think, very good because it generalizes and democratizes "magic". The idea is that, if you do
> 
> a = b + c;
> 
> and b + c does not make sense but b and c are ranges for which a.front = b.front + c.front does make sense, to automatically add the iteration paraphernalia.
> 
> 3. It was mentioned in this group that if getopt() does not work in SafeD, then SafeD may as well pack and go home. I agree. We need to make it work. Three ideas discussed with Walter:
> 
> * Allow taking addresses of locals, but in that case switch allocation from stack to heap, just like with delegates. If we only do that in SafeD, behavior will be different than with regular D. In any case, it's an inefficient proposition, particularly for getopt() which actually does not need to escape the addresses - just fills them up.
> 
> * Allow @trusted (and maybe even @safe) functions to receive addresses of locals. Statically check that they never escape an address of a parameter. I think this is very interesting because it enlarges the common ground of D and SafeD.
> 
> * Figure out a way to reconcile "ref" with variadics. This is the actual reason why getopt chose to traffic in addresses, and fixing it is the logical choice and my personal favorite.
> 
> 4. Allow private members inside a template using the eponymous trick:
> 
> template wyda(int x) {
>    private enum geeba = x / 2;
>    alias geeba wyda;
> }
> 
> The names inside an eponymous template are only accessible to the current instantiation. For example, wyda!5 cannot access wyda!(4).geeba, only its own geeba. That we we elegantly avoid the issue "where is this symbol looked up?"
> 
> 5. Chain exceptions instead of having a recurrent exception terminate the program. I'll dedicate a separate post to this.
> 
> 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.


7. opPow()

8. The magic "meta" namespace, aka. getting rid of is(typeof({...})) and __traits(...).


-Lars
November 19, 2009
Denis Koroskin wrote:
> On Thu, 19 Nov 2009 11:53:51 +0300, Don <nospam@nospam.com> wrote:
> 
>> 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).
> 
> I'd like for a..b to be auto-magically rewritten into range(a, b).

That's solution (2), but applied everywhere. One nice thing about that, is it would allow slices as function parameters. I've wanted that for:

testall(&sin, &asin, -PI..PI);

The downside is that you have to add this slice/range creature into std.object.
(BTW, note that $ would still only be allowable inside opIndex expressions. I can't see how it could be made to work in general, it's too uncertain which dimension you are referring to, especially in a variadic function).

November 19, 2009
Andrei Alexandrescu wrote:
> grauzone wrote:
>>
>> Also, you should fix the auto-flattening of tuples before it's too late. I think everyone agrees that auto-flattening is a bad idea, and that tuples should be nestable. Flattening can be done manually with an unary operator.
>>
>> (Introducing sane tuples (e.g. unify type and value tuples, sane and
>> short syntax, and all that) can wait for later if it must. Introducing
>> these can be downwards compatible, I hope.)
> 
> Non-flattening should be on the list but I am very afraid the solution would take a long time to design, implement, and debug. I must discuss this with Walter.
> 
> Andrei

Might I suggest a daring stop-gap:  kill tuples altogether.

Then we can implement them later correctly and it won't break backwards compatibility.

Ideally it isn't an all-or-nothing proposition either.  Maybe we can just kill the parts that are bad.  Like disallow tuples-of-tuples, but allow tuples that are already flat.

- Chad
November 19, 2009
Andrei Alexandrescu wrote:
> * 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. We need to design what happens with read-modify-write operators like "+=" (should they be dispatch to a different function? etc.) and also what happens with index-and-modify operators like "[]=", "[]+=" etc. Should we go with proxies? Absorb them in opBinary? Define another dedicated method? etc.
> 

Index-and-modify operators shouldn't exist.  That's the wrong place for it.

Indexing operators act like properties.  It is up to the types that they get and set to define the opXxxAssign overloads.

http://www.digitalmars.com/d/archives/digitalmars/D/A_possible_solution_for_the_opIndexXxxAssign_morass_98143.html#N98176

- Chad
November 19, 2009
Andrei Alexandrescu wrote:
> ...
> Any more thoughts, please let them known. ...
> 
> 
> Andrei

I like what I'm reading.  Pretty clever!

I imagine it'd be convenient to easily distinguish between pure binary operators and opAssign binary operators.  I can easily envision myself marking operator overloads such as + and * as pure to gain a performance boost.  Overloads like += and *= OTOH, often cannot be marked as pure.

- Chad
November 19, 2009
Don:
> The downside is that you have to add this slice/range creature into std.object.

I think this is acceptable. Slice structs look important enough. Adding a stride then looks simpler: Range(start, stop, stride).

Bye,
bearophile
November 19, 2009
On Thu, 19 Nov 2009 03:53:51 -0500, Don <nospam@nospam.com> wrote:

> 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]).

I hope you still mean to allow arguments other than int.

Also, how does this work with Andrei's "opBinary" proposal?

-Steve