digitalmars.D - opIndex() may hide opSlice()
- XavierAP (14/14) Mar 09 2017 In their versions without parameters, these two operators would
- H. S. Teoh via Digitalmars-d (11/30) Mar 09 2017 Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible
- XavierAP (6/15) Mar 09 2017 Yes but again, why not deprecate it, while still supporting it?
- H. S. Teoh via Digitalmars-d (11/28) Mar 09 2017 I'm not 100% as I wasn't part of the original discussion, but you could
- Nick Treleaven (7/19) Mar 10 2017 This seems non-intuitive to me (at least for single dimension
- Jonathan M Davis via Digitalmars-d (7/26) Mar 10 2017 Yeah, I've never understood how it made any sense for opIndex to be used...
- XavierAP (5/23) Mar 10 2017 I agree, the problem is that the current behavior prefers
- Adam D. Ruppe (6/8) Mar 10 2017 Yeah, I just saw that yesterday in a Phobos type and was like
- Patrick Schluter (6/35) Mar 10 2017 Indexing is a bit like slicing with only 1 element. Slicing is
- XavierAP (3/5) Mar 10 2017 The same call [] can go to a variadic opIndex(T[] indices ...)
- H. S. Teoh via Digitalmars-d (62/78) Mar 10 2017 It's very simple, really. Under the old behaviour, you have:
- jmh530 (16/19) Mar 10 2017 ndslice just recently added an indexed function
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (9/10) Mar 12 2017 Why is opSlice a method-operator?
- Jonathan M Davis via Digitalmars-d (25/100) Mar 10 2017 wrote:
- XavierAP (5/7) Mar 10 2017 https://www.mathworks.com/help/matlab/math/matrix-indexing.html#f1-85544
- H. S. Teoh via Digitalmars-d (49/72) Mar 10 2017 That's a subdimensional slice. In this case, we're dealing with a 2D
- Jonathan M Davis via Digitalmars-d (32/42) Mar 10 2017 As I said, for what _I_ deal with, it's useless. It's obviously useful t...
In their versions without parameters, these two operators would be called in the same way. If both are defined, opIndex() is called, regardless of lexical definition order, according to my test with DMD. If both (again I'm just talking about the overloads with no arguments) are defined within the same type, it's the fault of the author. But I would think the compiler should complain. They can also be inherited, which is sneakier. The web reference tersely says under its *Slice* Operator Overloading chapter [1]: "To overload a[], simply define opIndex with no parameters." Should not the overload of opSlice() with no arguments be deprecated? Am I missing something? [1] https://dlang.org/spec/operatoroverloading.html#array-ops
Mar 09 2017
On Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote:In their versions without parameters, these two operators would be called in the same way. If both are defined, opIndex() is called, regardless of lexical definition order, according to my test with DMD. If both (again I'm just talking about the overloads with no arguments) are defined within the same type, it's the fault of the author. But I would think the compiler should complain. They can also be inherited, which is sneakier. The web reference tersely says under its *Slice* Operator Overloading chapter [1]: "To overload a[], simply define opIndex with no parameters." Should not the overload of opSlice() with no arguments be deprecated? Am I missing something? [1] https://dlang.org/spec/operatoroverloading.html#array-opsUsing opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour. The recommended usage is to use opSlice to transform range syntax (i.e., arr[1 .. 2]) into a type that opIndex understands, i.e., arr[1, 2..3, 4] is translated to: arr.opIndex(1, arr.opSlice(2,3), 4) T -- "I'm running Windows '98." "Yes." "My computer isn't working now." "Yes, you already said that." -- User-Friendly
Mar 09 2017
On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote:On Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote:Yes but again, why not deprecate it, while still supporting it? It could same someone from having their code hidden and replaced in a polymorphic scenario. Granted it may not be a likely use case, but the situation seems to me to go against D's strong compile-time guarantees.Should not the overload of opSlice() with no arguments be deprecated? Am I missing something?Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour.
Mar 09 2017
On Fri, Mar 10, 2017 at 01:34:13AM +0000, XavierAP via Digitalmars-d wrote:On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote:I'm not 100% as I wasn't part of the original discussion, but you could file an enhancement request to this effect in the bug tracker. I think it makes sense to add a warning to the effect that opSlice() without arguments should be replaced by opIndex() without arguments in the next release, then turn it into a deprecation in the following release, and then an error in the one after that. But there may be some concerns about this that I'm not aware of. T -- Chance favours the prepared mind. -- Louis PasteurOn Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote:Yes but again, why not deprecate it, while still supporting it? It could same someone from having their code hidden and replaced in a polymorphic scenario. Granted it may not be a likely use case, but the situation seems to me to go against D's strong compile-time guarantees.Should not the overload of opSlice() with no arguments be deprecated? Am I missing something?Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour.
Mar 09 2017
On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote:On Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote: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..$])? Also deprecating nullary opSlice() would work against defining opSlice(int low = 0, int high = length).The web reference tersely says under its *Slice* Operator Overloading chapter [1]: "To overload a[], simply define opIndex with no parameters." Should not the overload of opSlice() with no arguments be deprecated? Am I missing something?Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour.
Mar 10 2017
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: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. 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. - Jonathan M DavisOn Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote: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..$])?The web reference tersely says under its *Slice* Operator Overloading chapter [1]: "To overload a[], simply define opIndex with no parameters." Should not the overload of opSlice() with no arguments be deprecated? Am I missing something?Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour.
Mar 10 2017
On Friday, 10 March 2017 at 15:41:31 UTC, Jonathan M Davis wrote:On Friday, March 10, 2017 14:15:45 Nick Treleaven via Digitalmars-d wrote:I agree, the problem is that the current behavior prefers opIndex(), so deprecating that one would break compatibility. Could be done in phases then. But this isn't really worth much bother of course.On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote: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. 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.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..$])?
Mar 10 2017
On Friday, 10 March 2017 at 15:41:31 UTC, Jonathan M Davis wrote: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.Yeah, I just saw that yesterday in a Phobos type and was like "wtf did they misname it"... but it worked. However, I can get used to it, [] going to opIndex() isn't really a stretch anyway, I would just like if it was better known and the compiler talked about it.
Mar 10 2017
On Friday, 10 March 2017 at 15:41:31 UTC, Jonathan M Davis wrote:On Friday, March 10, 2017 14:15:45 Nick Treleaven via Digitalmars-d wrote:Indexing is a bit like slicing with only 1 element. Slicing is the generalisation of the indexing operation. I think it's quite logical. This said I know nothing about the rationale and discussions about that subject. This was purely my wag (wild ass guess).On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote: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. 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. - Jonathan M DavisOn Fri, Mar 10, 2017 at 01:07:33AM +0000, XavierAP via Digitalmars-d wrote: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..$])?The web reference tersely says under its *Slice* Operator Overloading chapter [1]: "To overload a[], simply define opIndex with no parameters." Should not the overload of opSlice() with no arguments be deprecated? Am I missing something?Using opSlice() for slicing (i.e., arr[]) is old, backward-compatible behaviour.
Mar 10 2017
On Friday, 10 March 2017 at 14:15:45 UTC, Nick Treleaven wrote:Also deprecating nullary opSlice() would work against defining opSlice(int low = 0, int high = length).The same call [] can go to a variadic opIndex(T[] indices ...) So many possibilities :_)
Mar 10 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: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.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.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..$])?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
Mar 10 2017
On Friday, 10 March 2017 at 18:43:43 UTC, H. S. Teoh wrote: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.ndslice just recently added an indexed function that is like slicing based on some index. Other languages have something similar. However, it's not something that's built-in in D. Thus, given the indexed example: auto source = [1, 2, 3, 4, 5]; auto indexes = [4, 3, 1, 2, 0, 4].sliced; auto ind = source.indexed(indexes); there's way to instead write auto source = [1, 2, 3, 4, 5]; auto indexes = [4, 3, 1, 2, 0, 4].sliced; auto ind = source[indexes]; So to me, there does seem scope for some changes, even if they aren't the changes you mentioned in your post.
Mar 10 2017
On Friday, 10 March 2017 at 18:43:43 UTC, H. S. Teoh wrote:IndexRange opSlice(int x, int y) { ... }Why is opSlice a method-operator? Wouldn't it be better if you could do something more general auto idx_selection = (1,3,5); // select element 1, 3 and 5 auto idx_range = (0..4); // select element 0,1,2,3 auto idx_mixed = (0..3,4,5) // select 0,1,2,4,5 auto idx2d_range = [(0..4,5), (0..6)] // select intersections of rows and columns etc...
Mar 12 2017
On Friday, March 10, 2017 10:43:43 H. S. Teoh via Digitalmars-d wrote:On Fri, Mar 10, 2017 at 07:41:31AM -0800, Jonathan M Davis viaDigitalmars-d wrote:wrote:On Friday, March 10, 2017 14:15:45 Nick Treleaven via Digitalmars-dWell, thanks for the explanation, but I'm sure that part of the problem here is that an operation like arr[x, y..z] doesn't even make sense to me. I have no idea what that does. But I don't normally do anything with multidimensional arrays, and in the rare case that I do, I certainly don't need to overload anything for them. I just slap together a multidimensional array of whatever type it is I want in a multidimensional array. I can certainly understand that there are folks who really do care about this stuff, but it's completely outside of what I deal with, and for anything I've ever dealt with, making opIndex be for _slicing_ makes no sense whatsoever, and the added functionality to the language with regards to multi-dimensional arrays is useless. So, this whole mess has always felt like I've had something nonsensical thrown at me because of a use case that I don't even properly understand.[...]On Friday, 10 March 2017 at 01:10:21 UTC, H. S. Teoh wrote: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.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.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..$])?Well, I'd prefer that the original way be left, since that's all I've ever needed. If the new way makes life easier for the scientific programmers and whatnot, then great, but from the standpoint of anyone not trying to provide multi-dimensional overloads, using opIndex for slicing is just plain bizarre. That being said, I'm fine with the compiler detecting if opIndex and opSlice are declared in a way that they conflict and then giving an error. I just don't want to be forced to use opIndex for slicing. - Jonathan M DavisI 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.
Mar 10 2017
On Friday, 10 March 2017 at 20:36:35 UTC, Jonathan M Davis wrote:problem here is that an operation like arr[x, y..z] doesn't even make sense to me. I have no idea what that does.https://www.mathworks.com/help/matlab/math/matrix-indexing.html#f1-85544 You can stop reading as soon as it starts talking about "linear indexing". However if you're also curious what that means: https://www.mathworks.com/help/matlab/math/matrix-indexing.html#f1-85511
Mar 10 2017
On Fri, Mar 10, 2017 at 12:36:35PM -0800, Jonathan M Davis via Digitalmars-d wrote:On Friday, March 10, 2017 10:43:43 H. S. Teoh via Digitalmars-d wrote:[...]Well, thanks for the explanation, but I'm sure that part of the problem here is that an operation like arr[x, y..z] doesn't even make sense to me. I have no idea what that does.That's a subdimensional slice. In this case, we're dealing with a 2D array -- you can think of it as a matrix -- and extracting the y'th to z'th elements from column x. Conversely, arr[x..y, z] extracts the x'th to y'th elements from row z. This kind of subdimensional slicing is pretty common when you work with things like tensors.But I don't normally do anything with multidimensional arrays, and in the rare case that I do, I certainly don't need to overload anything for them. I just slap together a multidimensional array of whatever type it is I want in a multidimensional array.If by "multidimensional arrays" you mean arrays of arrays, then I can understand your sentiment. But when dealing with high-dimensional tensors, storing them explicitly may not always be the best approach. Think sparse matrices, for example. You want to be able to provide array indexing / slicing operations to user types apart from the built-in arrays. Not to mention that there are many problems with using arrays of arrays as "multidimensional" arrays, besides storage issues. One being that you can't easily represent a slice of an array of arrays across the minor dimension (i.e., a slice of every i'th element of each array in an int[][]). For things like that, you *really* want to be able to write arr[x, y..z] and arr[x..y, z] rather than arr[x][y..z] and arr[x..y][z]. Doing it the latter way means you need to implement arr.opSlice that returns a proxy type that implements opIndex. Kenji's design allows you to implement all of these cases (and more) by just implementing a single type with a single opSlice and single opIndex, and no proxy types, to boot. It's clean and elegant.I can certainly understand that there are folks who really do care about this stuff, but it's completely outside of what I deal with, and for anything I've ever dealt with, making opIndex be for _slicing_ makes no sense whatsoever, and the added functionality to the language with regards to multi-dimensional arrays is useless. So, this whole mess has always felt like I've had something nonsensical thrown at me because of a use case that I don't even properly understand.Please don't denigrate something as useless without at least trying to understand it first. [...]Well, I'd prefer that the original way be left, since that's all I've ever needed. If the new way makes life easier for the scientific programmers and whatnot, then great, but from the standpoint of anyone not trying to provide multi-dimensional overloads, using opIndex for slicing is just plain bizarre.Actually, it's the distinction between opSlice and opIndex in the old scheme that's what's bizarre. It's like saying that to implement userType(x) you need to declare userType.opSingleArgCall and to implement userType(x,y) you need to declare userType.opTwoArgCall, just because there happens to be 2 arguments instead of 1. Why not just unify the two under a single opCall, just with two overloads depending on what arguments you want to pass to it? In the same vein, requiring two different methods to implement arr[x] vs. arr[x..y] is bizarre. They should be unified under a single method -- I don't care what you call it, maybe opIndex is a bad name because it gives the wrong connotation for what it does, maybe it should be named opSquareBrackets or something. But the point is that this distinction between how arr[x] and arr[x..y] are handled is artificial and needless, and does not easily generalize to higher dimensions. Kenji's design is far superior.That being said, I'm fine with the compiler detecting if opIndex and opSlice are declared in a way that they conflict and then giving an error. I just don't want to be forced to use opIndex for slicing.[...] Nobody is forcing you to use opIndex for slicing right now, because the compiler currently accepts the old syntax for 1-dimensional arrays. And I already said it's probably a bad idea to deprecate the old syntax. T -- What do you mean the Internet isn't filled with subliminal messages? What about all those buttons marked "submit"??
Mar 10 2017
On Friday, March 10, 2017 14:07:59 H. S. Teoh via Digitalmars-d wrote:On Fri, Mar 10, 2017 at 12:36:35PM -0800, Jonathan M Davis viaDigitalmars-d wrote:As I said, for what _I_ deal with, it's useless. It's obviously useful to some subset of programmers - particularly folks doing scientific stuff based on what I've seen about posts about multi-dimensional arrays - but it's useless to me. I did not mean to denigrate anything, so sorry if that wasn't clear. My point is that I don't want to have to worry about it or be affected by it when it is useless to me - particularly when I'd have to spend some time studying it to understand it properly. About the only time I've dealt with matrices in any real way was when I took linear algebra, and I've forgotten almost everything from that class. It simply has nothing to do with anything that I've ever needed to program, and I'd just as soon avoid any kind of math that would require it. So, as long as the multi-dimensional slicing stuff sits in its own little corner of the language where I don't have to deal with it, I'm fine. I just want to keep using opSlice for slicing and opIndex for indexing, because that makes sense to me and my needs, whereas using opIndex for a slicing operation just seems wrong, much as it apparently has a benefit for generic code dealing with multi-dimensional indexing and slicing. And as long as the current situation with opSlice working for slicing like it always has continues, I don't care what the subset of programmers who care about multi-dimensional arrays do with opIndex. Unfortunately, it comes up periodically that someone pushes for everything to change to use opIndex for slicing or even to deprecate opSlice for slicing even when the code has nothing to do with multi-dimensional indexing or slicing, and I do object to that. If no multi-dimensional indexing or slicing is involved, I think that opSlice should be used for slicing, not opIndex. Fortunately though, there hasn't been a real push to move everything to use opIndex instead of opSlice and get rid of the original behavior, and I hope that that stays the case. Regardless, thank you for your thorough explanation as to why it was changed so that opIndex could be used for a slicing operation. - Jonathan M DavisI can certainly understand that there are folks who really do care about this stuff, but it's completely outside of what I deal with, and for anything I've ever dealt with, making opIndex be for _slicing_ makes no sense whatsoever, and the added functionality to the language with regards to multi-dimensional arrays is useless. So, this whole mess has always felt like I've had something nonsensical thrown at me because of a use case that I don't even properly understand.Please don't denigrate something as useless without at least trying to understand it first.
Mar 10 2017