Re: opSlice() or opIndex() for the entire slice?

2014-09-09 Thread H. S. Teoh via Digitalmars-d-learn
On Thu, Sep 04, 2014 at 09:04:33PM +, monarch_dodra via Digitalmars-d-learn 
wrote:
 On Thursday, 4 September 2014 at 19:12:27 UTC, Ali Çehreli wrote:
 The documentation says To overload a[], simply define opIndex with
 no parameters:
 
   http://dlang.org/operatoroverloading.html#Slice
 
 And it works with some uses of a[]. However, opSlice() seems to be
 needed to actually use the returned slice further.
 
 This must be new, as I've read that page several times, and have never
 seen that before. Furthermore, I've done a *lot* pulls to improve
 ranges' slicing capabilities, and no reviewer has ever mentioned this
 before.

I'm the one responsible for this. It's based on Kenji's PR that
implements multidimensional slicing; in his design, he specifically
relegates opSlice to returning index range proxy objects, so that mixed
single-indexing and range-indexing (i.e., expressions of the form A[1,
2..3, 4, 5..6]) will be easily implementable. Under this design, x..y is
translated into A.opSlice(x,y), and the actual indexing with [] is
translated into A.opIndex(...), so in this case it becomes:

A.opIndex(1, A.opSlice(2,3), 4, A.opSlice(5,6));

The idea, really, is that by this unification we can write a generic
opIndex that handles any combination of single indices and index ranges:

struct Array {
ProxyObj opSlice(int i, int j) { ... }

void opIndex(A...)(A args) {
foreach (arg; args) {
static if (is(typeof(arg) == ProxyObj))
{
// implement index range here
}
else if (is(typeof(arg) : size_t))
{
// implement single index lookup
// here
}
}
}
}

By extension, therefore, it follows that implementing slice operations
of the form A[] simply involves invoking A.opIndex() with no arguments.

Of course, to maintain backward compatibility, Kenji's PR left the old
semantics of opSlice intact, if no suitable implementation of opIndex is
found for expressions of the form A[]. So this is what I documented on
that page.


T

-- 
Question authority. Don't ask why, just do it.


Re: opSlice() or opIndex() for the entire slice?

2014-09-09 Thread Ali Çehreli via Digitalmars-d-learn

On 09/09/2014 09:56 PM, H. S. Teoh via Digitalmars-d-learn wrote:

 Kenji's PR that
 implements multidimensional slicing; in his design, he specifically
 relegates opSlice to returning index range proxy objects

That's exactly how it finally made sense to me. :)

 By extension, therefore, it follows that implementing slice operations
 of the form A[] simply involves invoking A.opIndex() with no arguments.

I am currently writing an update to my Operator Overloading chapter to 
explain all of that.


Ali



Re: opSlice() or opIndex() for the entire slice?

2014-09-07 Thread Ali Çehreli via Digitalmars-d-learn

I think I figured this one out.

Before 2.066, we did not have proper support for multi-dimensional 
slicing. The following were the semantics we had:


-- The opIndex() overloads provided access to direct elements. Since 
multi-dimensional support was incomplete, opIndex() was about accessing 
a single object:


a[i] // element i by opIndex(size_t)
a[i, j]  // element i,j by opIndex(size_t, size_t)

-- The two overloads of opSlice() returned range objects that 
represented either all of the elements or a slice of them:


a[]// opSlice()
a[i..j]// opSlice(size_t, size_t)

Note that those features are still usable but according to 
documentation, they are discouraged.


Since 2.066, we have multi-dimensional slicing. The way we look at these 
operators have changed a little:


-- The opIndex() overloads now return ranges that represent a range of 
elements. For example, if 'a' is a two-dimensional matrix, the first 
line below should return a sub-matrix inside it, not a single element:


a[i..j, k..l]// opIndex(MyRange, MyRange)

The following can indeed return a single element but I think it is a 
valid design decision to return a sub-matrix consisting of a single 
element as well:


a[i, j]// a matrix of one element by opIndex(size_t size_t)

A single matrix row:

a[i, j..j]   // opIndex(size_t, MyRange)

Here is the answer to my original question: Consistent with the above, 
now opIndex() must take the responsibility of returning all of the elements:


a[]   // opIndex()

-- With that change of responsibility, what remains for opSlice() is the 
only task of producing a range object that opIndex() can later use to 
represent one or more elements:


a[i..j, k..l]  // opIndex(opSlice(i, j), opSlice(k, l))

In summary, the following is what opSlice() should do almost in all cases:

Tuple!(size_t size_t) opSlice(size_t beg, size_t end)
{
return tuple(beg, end);
}

Also note that representing i..j and k..l can always be done by a 
Tuple!(size_t, size_t) without loss of any information. (i.e. MyRange 
above can actually be Tuple!(size_t, size_t)):


I am attaching an example that helped me understand what was going on. 
Note that the program is decidedly elegant as opposed to efficient. 
:) For example, even initializing a single element by random access goes 
through these steps:


Initializing 1 of 80
deneme.Matrix.opIndexAssign!(int, int).opIndexAssign
deneme.Matrix.opIndex!(int, int).opIndex
deneme.Matrix.opDollar!0LU.opDollar
deneme.Matrix.opDollar!1LU.opDollar
deneme.Matrix.subMatrix
deneme.Matrix.this
deneme.Matrix.opAssign
[...]

But I like it. :)

Ali

import std.stdio;
import std.format;
import std.string;

struct Matrix
{
private:

int[][] rows;

/* Represents a range of rows of columns. */
struct Range
{
size_t beg;
size_t end;
}

/* Returns a reference to a sub-matrix that correspond to
 * the range arguments. */
Matrix subMatrix(Range rowRange, Range columnRange)
{
writeln(__FUNCTION__);

int[][] slices;

foreach (row; rows[rowRange.beg .. rowRange.end]) {
slices ~= row[columnRange.beg .. columnRange.end];
}

return Matrix(slices);
}

public:

this(size_t height, size_t width)
{
writeln(__FUNCTION__);

rows = new int[][](height, width);
}

this(int[][] rows)
{
writeln(__FUNCTION__);

this.rows = rows;
}

void toString(void delegate(const(char)[]) sink) const
{
formattedWrite(sink, %(%(%5s %)\n%), rows);
}

/* Assigns the value to all of the elements of the
 * matrix. */
Matrix opAssign(int value)
{
writeln(__FUNCTION__);

foreach (row; rows) {
row[] = value;
}

return this;
}

/* Applies the operation to each element and assigns the
 * result back to it. e.g. 'm += 42'*/
Matrix opOpAssign(string op)(int value)
{
writeln(__FUNCTION__);

foreach (row; rows) {
mixin (row[]  ~ op ~ = value;);
}

return this;
}

/* Returns the size of the provided dimension. */
size_t opDollar(size_t dimension)() const
{
writeln(__FUNCTION__);

static if (dimension == 0) {
return rows.length;

} else static if (dimension == 1) {
return rows.length ? rows[0].length : 0;

} else {
static assert(false,
  format(Invalid dimension: %s,
 dimension));
}
}

/* Returns a range representing the provided indices. */
Range opSlice(size_t dimension)(size_t beg, size_t end)
{
writeln(__FUNCTION__);

return Range(beg, end);
}

/* Returns a sub-matrix corresponding to the arguments. */
Matrix opIndex(A...)(A args)
{

opSlice() or opIndex() for the entire slice?

2014-09-04 Thread Ali Çehreli via Digitalmars-d-learn
The documentation says To overload a[], simply define opIndex with no 
parameters:


  http://dlang.org/operatoroverloading.html#Slice

And it works with some uses of a[]. However, opSlice() seems to be 
needed to actually use the returned slice further.


Note that opSlice() also seems to be for backward compatibility: For 
backward compatibility, a[] and a[i..j] can also be overloaded by 
implementing opSlice() with no arguments and opSlice(i, j) with two 
arguments, respectively. This only applies for one-dimensional slicing, 
and dates from when D did not have full support for multidimensional 
arrays. This usage of opSlice is discouraged.


How can I achieve the last line in main() without needing opSlice()? I 
am trying to let the Slice type handle opAssign() and friends.


import std.stdio;

struct Collection
{
int[] elements;

/* Handles the slice operations */
struct Slice
{
int[] slice;

Slice opAssign(int value)
{
slice[] = value;
return this;
}
}

Slice opIndex()
{
writeln(opIndex);
return Slice(elements);
}

Slice opSlice()
{
writeln(opSlice);
return Slice(elements);
}
}

void main()
{
auto c = Collection([ 0, 1, 2, 3]);

// This works with either operator but opIndex is favored. (You can
// comment out opSlice() and it will still work.)
writeln(c[]);

// This requires opSlice.
c[] = 42;
}

Ali


Re: opSlice() or opIndex() for the entire slice?

2014-09-04 Thread via Digitalmars-d-learn

On Thursday, 4 September 2014 at 19:12:27 UTC, Ali Çehreli wrote:
The documentation says To overload a[], simply define opIndex 
with no parameters:


  http://dlang.org/operatoroverloading.html#Slice

And it works with some uses of a[]. However, opSlice() seems to 
be needed to actually use the returned slice further.


Note that opSlice() also seems to be for backward 
compatibility: For backward compatibility, a[] and a[i..j] can 
also be overloaded by implementing opSlice() with no arguments 
and opSlice(i, j) with two arguments, respectively. This only 
applies for one-dimensional slicing, and dates from when D did 
not have full support for multidimensional arrays. This usage 
of opSlice is discouraged.


How can I achieve the last line in main() without needing 
opSlice()? I am trying to let the Slice type handle opAssign() 
and friends.




--8-- snip --8--


// This requires opSlice.
c[] = 42;
}


Have you tried opIndexAssign()?


Re: opSlice() or opIndex() for the entire slice?

2014-09-04 Thread monarch_dodra via Digitalmars-d-learn

On Thursday, 4 September 2014 at 19:12:27 UTC, Ali Çehreli wrote:
The documentation says To overload a[], simply define opIndex 
with no parameters:


  http://dlang.org/operatoroverloading.html#Slice

And it works with some uses of a[]. However, opSlice() seems to 
be needed to actually use the returned slice further.


This must be new, as I've read that page several times, and have 
never seen that before. Furthermore, I've done a *lot* pulls to 
improve ranges' slicing capabilities, and no reviewer has ever 
mentioned this before.


Re: opSlice() or opIndex() for the entire slice?

2014-09-04 Thread Ali Çehreli via Digitalmars-d-learn

On 09/04/2014 12:19 PM, Marc Schütz schue...@gmx.net wrote:

On Thursday, 4 September 2014 at 19:12:27 UTC, Ali Çehreli wrote:

The documentation says To overload a[], simply define opIndex with no
parameters:

  http://dlang.org/operatoroverloading.html#Slice

And it works with some uses of a[]. However, opSlice() seems to be
needed to actually use the returned slice further.

Note that opSlice() also seems to be for backward compatibility: For
backward compatibility, a[] and a[i..j] can also be overloaded by
implementing opSlice() with no arguments and opSlice(i, j) with two
arguments, respectively. This only applies for one-dimensional
slicing, and dates from when D did not have full support for
multidimensional arrays. This usage of opSlice is discouraged.

How can I achieve the last line in main() without needing opSlice()? I
am trying to let the Slice type handle opAssign() and friends.



--8-- snip --8--


// This requires opSlice.
c[] = 42;
}


Have you tried opIndexAssign()?


That works and kind of makes sense.

My main confusion stems from the fact that the new templated opSlice() 
does not have its own section on the documentation page, other than 
appearing in examples of other operators.


What I did not like about opIndexAssign() at first is the fact that it 
puts the Collection in the middle and requires an explicit dispatch by 
the programmer to Slice. In contrast, the legacy way (which still works) 
bypasses Collection and goes directly to Slice.


However, the legacy way supports only one dimension.

Also, the new way is necessary as the same function (namely, 
opIndexAssign) takes care of both indexing and slicing. This is from the 
change log:


void opIndexAssign(A...)(E val, A indices)
{
assert(A.length == payload.length);

foreach (dim, x; indices)
{
static if (is(typeof(x) : size_t))
{
// this[..., x, ...]
payload[dim][x] = val;
}
else
{
// this[..., x[0] .. x[1], ...]
payload[dim][x[0] .. x[1]] = val;
}
}
}

So, the following is my current solution. Note that opIndex() seems 
still be necessary. Is it possible to replace it with an opSlice overload?


import std.stdio;

struct Collection
{
int[] elements;

/* Handles the slice operations */
struct Slice
{
int[] slice;

Slice opAssign(int value)
{
writefln(Slice.opAssign(int));
slice[] = value;
return this;
}
}

size_t opDollar() const
{
writefln(opDollar());
return elements.length;
}

// CHALLENGE: Can you replace this with an opSlice overload?
Slice opIndex()
{
writefln(opIndex());
return Slice(elements);
}

Slice opSlice(size_t dim, A...)(A args)
{
writefln(opSlice!%s(%(%s, %)), dim, [ args ]);
return Slice(elements[args[0] .. args[1]]);
}

Collection opIndexAssign(A...)(int value, A indexes)
{
writefln(opIndexAssign(%s, %(%s, %)), value, [ indexes ]);

foreach (dim, x; indexes)
{
static if (is(typeof(x) : size_t))
{
elements[x] = value;

} else static if (is(typeof(x) : Slice)) {
x = value;

} else {
static assert(false);
}
}

return this;
}
}

void main()
{
auto c = Collection([ 0, 1, 2, 3]);

writeln(\n--- c[] ---);
c[];

writeln(\n--- c[1 .. $-1] = 42 ---);
c[1 .. $-1] = 42;

writeln(\n--- c[3] = 100 ---);
c[3] = 100;

assert(c.elements == [ 0, 42, 42, 100 ]);
}

The output:

--- c[] ---
opIndex()

--- c[1 .. $-1] = 42 ---
opDollar()
opSlice!0(1, 3)
opIndexAssign(42, Slice([1, 2]))
Slice.opAssign(int)

--- c[3] = 100 ---
opIndexAssign(100, 3)

Ali



Re: opSlice() or opIndex() for the entire slice?

2014-09-04 Thread Ali Çehreli via Digitalmars-d-learn

On 09/04/2014 02:04 PM, monarch_dodra wrote:

 On Thursday, 4 September 2014 at 19:12:27 UTC, Ali Çehreli wrote:
 The documentation says To overload a[], simply define opIndex with no
 parameters:

   http://dlang.org/operatoroverloading.html#Slice

 And it works with some uses of a[]. However, opSlice() seems to be
 needed to actually use the returned slice further.

 This must be new, as I've read that page several times, and have never
 seen that before. Furthermore, I've done a *lot* pulls to improve
 ranges' slicing capabilities, and no reviewer has ever mentioned this
 before.

Yeah, there is something fishy with these operators.

Please see my other post. It seems like opIndex() is necessary or at 
least more convenient.


Ali