opIndex() may hide opSlice()

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Fri Mar 10 10:43:43 PST 2017


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


More information about the Digitalmars-d mailing list