On Fri, Mar 10, 2017 at 07:41:31AM -0800, Jonathan M Davis via Digitalmars-d 
wrote:
> On Friday, March 10, 2017 14:15:45 Nick Treleaven via Digitalmars-d wrote:
> > On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote:
[...]
> > > Using opSlice() for slicing (i.e., arr[]) is old,
> > > backward-compatible behaviour.
> >
> > This seems non-intuitive to me (at least for single dimension
> > containers) - when you see var[], do you think var is being
> > indexed or do you think var is being sliced like an array
> > (equivalent to var[0..$])?
> 
> Yeah, I've never understood how it made any sense for opIndex to be
> used for slicing, and I've never used it that way.

It's very simple, really.  Under the old behaviour, you have:

        arr[]           --->    arr.opSlice()
        arr[x]          --->    arr.opIndex(x)
        arr[x..y]       --->    arr.opSlice(x,y)

This made implementing higher-dimensional slicing operators hard to
define, especially if you want mixed slicing and indexing (aka
subdimensional slicing):

        arr[x, y]       --->    arr.opIndex(x, y)
        arr[x, y..x]    --->    ?
        arr[x..y, z]    --->    ?
        arr[w..x, y..z] --->    arr.opSlice(w, x, y, z)  // ?

Kenji's insight was that we can solve this problem by homogenizing
opSlice and opIndex, such that [] *always* translates to opIndex, and ..
always translates to opSlice.

So, under the new behaviour:

        arr[]           --->    arr.opIndex()
        arr[x]          --->    arr.opIndex(x)
        arr[x,y]        --->    arr.opIndex(x,y)

        arr[x..y]       --->    arr.opIndex(arr.opSlice(x,y))
        arr[x, y..z]    --->    arr.opIndex(x, arr.opSlice(y,z))
        arr[x..y, z]    --->    arr.opIndex(arr.opSlice(x,y), z)

This allows mixed-indexing / subdimensional slicing to consistently use
opIndex, with opSlice returning objects representing index ranges, so
that in a multidimensional user type, you could unify all the cases
under a single definition of opIndex:

        IndexRange opSlice(int x, int y) { ... }

        auto opIndex(I...)(I indices)
        {
                foreach (idx; indices) {
                        static if (is(typeof(idx) == IndexRange))
                        {
                                // this index is a slice
                        }
                        else
                        {
                                // this index is a single index
                        }
                }
        }

Without this unification, you'd have to implement 2^n different
overloads of opIndex / opSlice in order to handle all cases of
subdimensional slicing in n dimensions.

So you can think of it simply as:

        []      ==      opIndex
        ..      ==      opSlice

in all cases.

It is more uniform this way, and makes perfect sense to me.


> I generally forget that that change was even made precisely because it
> makes no sense to me, whereas using opSlice for slicing makes perfect
> sense. I always use opIndex for indexing and opSlice for slicing just
> like they were originally designed.
[...]

This is probably why Kenji didn't deprecate the original use of opSlice,
since for the 1-dimensional case the homogenization of opSlice / opIndex
is probably unnecessary and adds extra work for the programmer: if you
want to implement arr[x..y] you have to write both opSlice and an
opIndex overload that accepts what opSlice returns, as opposed to just
writing a single opSlice.

So probably we should leave it the way it is (and perhaps clarify that
in the spec), as deprecating the "old" use of opSlice in the
1-dimensional case would cause problems.


T

-- 
Chance favours the prepared mind. -- Louis Pasteur

Reply via email to