www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Repost: make foreach(i, a; range) "just work"

reply "Regan Heath" <regan netmail.co.nz> writes:
I am posting this again because I didn't get any feedback on my idea,  
which may be TL;DR or because people think it's a dumb idea and they were  
politely ignoring it :p

My original thought was that things like this should "just work"..

auto range = input.byLine();
while(!range.empty)
{
   range.popFront();
   foreach (i, line; range.take(4))  //Error: cannot infer argument types
   {
     ..etc..
   }
   range.popFront();
}

The reason it fails was best expressed by Steven:

 This is only available using opApply style iteration. Using range  
 iteration does not give you this ability.
 It's not a permanent limitation per se, but there is no plan at the  
 moment to add multiple parameters torange iteration.

 One thing that IS a limitation though: we cannot overload on return  
 values. So the obvious idea ofoverloading front to return tuples of  
 various types, would not be feasible. opApply can do that becausethe  
 delegate is a parameter.
And Jakob pointed me to this proposed solution: [1] https://github.com/D-Programming-Language/phobos/pull/1866 Which is a great idea, but, I still feel that this should "just work" as I have written it. I think this is what people will intuitively expect to work, and having it fail and them scrabble around looking for enumerate is sub-optimal. I think we can solve it without negatively impacting future plans like what bearophile wants, which is built-in tuples (allowing foreach over AA's etc). So, the solution I propose for my original problem above is: Currently the 'i' value in a foreach on an array is understood to be an index into the array. But, ranges are not always indexable. So, for us to make this work for all ranges we would have to agree to change the meaning of 'i' from being an "index" to being a "counter, which may also be an index". This counter would be an index if the source object was indexable. Another way to look at it is to realise that the counter is always an index into the result set itself, and could be used as such if you were to store the result set in an indexable object. To implement this, foreach simply needs to keep a counter and increment it after each call to the foreach body - the same way (I assume) it does for arrays and objects with opApply. Interestingly, if this had been in place earlier, then the byKey() and byValue() members of AA's would not have been necessary. Instead keys/values could simply have changed into indexable ranges, and no code breakage would have occurred (AFAICS). So, to address bearophile's desire for built-in tuples, and iteration over AA's and how this change might affect those plans. It seems to me we could do foreach over AAs/tuples in one of 2 ways or even a combination of both: Scheme 1) for AA's/tuples the value given to the foreach body is a voldemort (unnamed) type with a public property member for each component of the AA/tuple. In the case of AA's this would then be "key" and "value", for tuples it might be a, b, .., z, aa, bb, .. and so on. foreach(x; AA) {} // Use x.key and x.value foreach(i, x; AA) {} // Use i, x.key and x.value foreach(int i, x; AA) {} // Use i, x.key and x.value Extra/better: For non-AA tuples we could allow the members to be named using some sort of syntax, i.e. foreach(i, (x.bob, x.fred); AA) {} // Use i, x.bob and x.fred or foreach(i, x { int bob; string fred }; AA) {} // Use i, x.bob and x.fred or foreach(i, new x { int bob; string fred }; AA) {} // Use i, x.bob and x.fred foreach (v; AA) {} foreach (x; AA) { .. use x.value .. } // better? worse? foreach (k, v; AA) {} foreach (x; AA) { .. use x.key, x.value .. } // better? worse? foreach (k; AA.byKeys) {} same // no voldemort reqd foreach (i, k; AA.byKeys.enumerate) {} foreach (i, k; AA.byKeys) {} // better. note, no voldemort reqd foreach (i, v; AA.byValues.enumerate) {} foreach (i, v; AA.byValues) {} // better. note, no voldemort reqd foreach (k, v; AA.byPairs) {} foreach (x; AA) { .. use x.key, x.value .. } // better foreach (i, k, v; AA.byPairs.enumerate) {} foreach (i, x; AA) { .. use i and x.key, x.value .. } // better This is my preferred approach TBH, you might call it foreach on "packed" tuples. Scheme 2) the tuple is unpacked into separate variables given in the foreach. When no types are given, components are assigned to variables such that the rightmost is the last AA/tuple component and subsequent untyped variables get previous components up and until the N+1 th which gets index/count. foreach (v; AA) {} // v is "value" (last tuple component) foreach (k, v; AA) {} // k is "key" (2nd to last tuple component), ... foreach (i, k, v; AA) {} // i is "index/count" because AA only has 2 tuple components. So, if you have N tuple components and you supply N+1 variables you get the index/count. Supplying any more would be an error. However, if a type is given and the type can be unambiguously matched to a single tuple component then do so. double[string] AA; foreach (string k; AA) {} // k is "key" .. in which case, any additional unmatched untyped or 'int' variable is assigned the index/count. e.g. foreach (i, double v; AA) {} // i is index, v is "value" foreach (i, string k; AA) {} // i is index, k is "key" If more than one typed variable is given, match each unambiguously. foreach (string k, double v; AA) {} // i is index, k is "key", v is "value" .. and likewise any unmatched untyped or 'int' variable is assigned index/count. e.g. foreach (i, string k, double v; AA) {} // i is index, k is "key", v is "value" foreach (int i, string k, double v; AA) {} // i is index, k is "key", v is "value" Any ambiguous situation would result in an error requiring the use of one of .keys/values (in the case of an AA), or to specify types (where possible), or to specify them all in the tuple order, e.g. Using a worst case of.. int[int] AA; // Error: cannot infer binding of k; could be 'key' or 'value' foreach (int k; AA) {} // Solve using .keys/byKey()/values/byValue() foreach (k; AA.byKeys) {} // k is "key" foreach (i, k; AA.byKeys) {} // i is index/count, k is "key" // Solve using tuple order foreach (k, v; AA) {} // k is "key", v is "value" foreach (i, k, v; AA) {} // i is index/count, k is "key", v is "value" foreach (v; AA) {} same foreach (k, v; AA) {} same foreach (k; AA.byKeys) {} same foreach (i, k; AA.byKeys.enumerate) {} foreach (i, k; AA.byKeys) {} // better foreach (i, v; AA.byValues.enumerate) {} foreach (i, v; AA.byValues) {} // better foreach (k, v; AA.byPairs) {} foreach (k, v; AA) {} // better foreach (i, k, v; AA.byPairs.enumerate) {} foreach (i, k, v; AA) {} // better Scheme 3) Combination. If we were to combine these ideas we would need to - when more than 2 variables are given, or - a specific type is given for the final variable. double[string] AA; value)/tuple value)/tuple value key, v is value With.. types even worst case "all one type" (A) int[int] AA; // worst case (B) double[string] AA; foreach (v; AA) {} (*) foreach (x; AA) { .. use x.value .. } // better? (A) foreach (i, k, v; AA) { } // worse? (B) foreach (double v; AA) { } // worse? foreach (k, v; AA) {} (*) foreach (x; AA) { .. use x.key, x.value .. } // better? foreach (k; AA.byKeys) {} same // note, no voldemort reqd foreach (i, k; AA.byKeys.enumerate) {} (*) foreach (i, k; AA.byKeys) {} // better, note; no voldemort reqd foreach (i, v; AA.byValues.enumerate) {} (*) foreach (i, v; AA.byValues) {} // netter, note; no voldemort reqd foreach (k, v; AA.byPairs) {} (*) foreach (x; AA) { .. use x.key, x.value .. } // better foreach (i, k, v; AA.byPairs.enumerate) {} (*) foreach (i, x; AA) { .. use i and x.key, x.value .. } // better (A) foreach (i, k, v; AA) { } // better (B) foreach (i, k, v; AA) { } // better *********** Thoughts? Regan -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 20 2014
next sibling parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
IMO, any change needs to be both backwards-compatible (i.e., it 
should not only "just work", as you phrased, but existing code 
should "just keep working"), and forward-compatible, so as not to 
obstruct any potential improvements of tuple handling.




specified are too complicated. Instead, I would suggest just to 
always assign the variables from the right, i.e. you cannot skip 
variables, and if you specify a type, it must match the type of 
the value in this position.

If you really want to skip a tuple member (in order to avoid an 
expensive copy), a special token "_" or "$" could be introduced, 
as has also been suggested in one the tuple unpacking/pattern 
matching DIPs, IIRC.

As for unpacking a tuple value (or key), an additional pair of 
parentheses can be used, so such a feature would still be 
possible in the future:

foreach(i, k, (a,b,c); ...)


important to be intuitively understandable and predictable.)
Feb 20 2014
parent "Regan Heath" <regan netmail.co.nz> writes:
On Thu, 20 Feb 2014 12:56:27 -0000, Marc Sch=FCtz <schuetzm gmx.net> wro=
te:

 IMO, any change needs to be both backwards-compatible (i.e., it should=
=
 not only "just work", as you phrased, but existing code should "just  =
 keep working"), and forward-compatible, so as not to obstruct any  =
 potential improvements of tuple handling.


Fair enough. We can always pack things manually using something like th= e = enumerate() method mentioned in the link.

d =
 are too complicated. Instead, I would suggest just to always assign th=
e =
 variables from the right, i.e. you cannot skip variables, and if you  =
 specify a type, it must match the type of the value in this position.

 If you really want to skip a tuple member (in order to avoid an  =
 expensive copy), a special token "_" or "$" could be introduced, as ha=
s =
 also been suggested in one the tuple unpacking/pattern matching DIPs, =
=
 IIRC.

 As for unpacking a tuple value (or key), an additional pair of  =
 parentheses can be used, so such a feature would still be possible in =
=
 the future:

 foreach(i, k, (a,b,c); ...)
Cool.

be =
 intuitively understandable and predictable.)
Fair enough. Any comments on the initial solution to my original problem? R -- = Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 20 2014
prev sibling next sibling parent reply "w0rp" <devw0rp gmail.com> writes:
I don't think this is a good idea. Say you have a class with 
range methods and add opApply later. Only the opApply delegate 
receives a type other than size_t for the first argument. Now the 
foreach line infers a differnt type for i and code in the outside 
world will break.

More importantly, this gets in the way of behaviour which may be 
desirable later, foreach being able to unpack tuples from ranges. 
I would like if it was possible to return Tuple!(A, B) from 
front() and write foreach(a, b; range) to interate through those 
thing, unpacking the values with an alias, so this...

foreach(a, b; range) {
}

... could rewrite to roughly this. (There may be a better way.)

foreach(_someInternalName; range) {
     alias a = _someInternalName[0];
     alias b = _someInternalName[1];
}

Then to get a counter with a range, we could follow Python's 
example and use an enumerate function, which would take an 
existing range and wrap it with a counter, so T maps to 
Tuple!(size_t, T).

foreach(index, value; enumerate(range)) {
}

Which is rewritten to roughly this.

foreach(_bla; enumerate(range)) {
     alias index = _bla[0];
     alias value = _bla[1];
}

If we follow Python's example again, we could also support this 
nested unpacking.

// Now written with UFCS instead.
foreach(index, (index_again, value); range.enumerate.enumerate) {
}

Which can rewrite to roughly this.

foreach(_bla; range.enumerate.enumerate) {
     alias index = _bla[0];
     alias index_again = _bla[1][0];
     alias value = _bla[1][1];
}

I got off on kind of a tangent there, but there you go.
Feb 20 2014
next sibling parent "w0rp" <devw0rp gmail.com> writes:
I probably didn't do enough job of reading your post because it 
looks like you shared some similar ideas. I'm sorry if my post 
reads a little like that.
Feb 20 2014
prev sibling next sibling parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Thu, 20 Feb 2014 13:04:55 -0000, w0rp <devw0rp gmail.com> wrote:

 I don't think this is a good idea.
Which part? The initial solution to my initial problem, or one of the 3 schemes mentioned?
 Say you have a class with range methods and add opApply later. Only the  
 opApply delegate receives a type other than size_t for the first  
 argument. Now the foreach line infers a differnt type for i and code in  
 the outside world will break.
Only if the compiler prefers opApply to range methods, does it? And, if it prefers range methods then any existing class with opApply (with more than 1 variable) that gets range methods will break also, because foreach(<more than 1 variable>; range) does not (currently) work.
 More importantly, this gets in the way of behaviour which may be  
 desirable later, foreach being able to unpack tuples from ranges.
<snip> :) R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 20 2014
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz>  
wrote:

 Only if the compiler prefers opApply to range methods, does it?
It should. If it doesn't, that is a bug. The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply. -Steve
Feb 20 2014
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Thu, 20 Feb 2014 17:09:31 -0000, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz>  
 wrote:

 Only if the compiler prefers opApply to range methods, does it?
It should. If it doesn't, that is a bug. The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply.
Thanks. So, if we had this support which I am asking for: foreach(index, value; range) { } And, if someone adds opApply to that range, with a different type for the first variable then an existing foreach (using index, value) is likely to stop compiling due to type problems. This seems acceptable to me. There is an outside chance it might keep on compiling, like if 'i' is not used in a strongly typed way, i.e. passed to a writefln or similar. In this case we have silently changed behaviour. Is this acceptable? R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 21 Feb 2014 06:21:39 -0500, Regan Heath <regan netmail.co.nz>  
wrote:

 On Thu, 20 Feb 2014 17:09:31 -0000, Steven Schveighoffer  
 <schveiguy yahoo.com> wrote:

 On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz>  
 wrote:

 Only if the compiler prefers opApply to range methods, does it?
It should. If it doesn't, that is a bug. The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply.
Thanks. So, if we had this support which I am asking for: foreach(index, value; range) { } And, if someone adds opApply to that range, with a different type for the first variable then an existing foreach (using index, value) is likely to stop compiling due to type problems. This seems acceptable to me.
I think any type that does both opApply and range iteration is asking for problems :) D has a nasty way of choosing "all or nothing" for overloads, meaning it may decide "this is a range" or "this is opApply", but if you have both, it picks one or the other. I'd rather see it do: 1. can I satisfy this foreach using opApply? If yes, do it. 2. If not, can I satisfy this foreach using range iteration? This may be how it works, I honestly don't know.
 There is an outside chance it might keep on compiling, like if 'i' is  
 not used in a strongly typed way, i.e. passed to a writefln or similar.   
 In this case we have silently changed behaviour.

 Is this acceptable?
Adding opApply is changing the API of the range. If the range does something different based on whether you use the range interface or opApply, then this is a logic error IMO. The easiest thing is to just not use opApply and range primitives together :) One separation I like to use in my code is that you use opApply on a container, but range primitives on a range for that container. And a container is not a range. -Steve
Feb 21 2014
next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
On Fri, 21 Feb 2014 14:29:37 -0000, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Fri, 21 Feb 2014 06:21:39 -0500, Regan Heath <regan netmail.co.nz>  
 wrote:

 On Thu, 20 Feb 2014 17:09:31 -0000, Steven Schveighoffer  
 <schveiguy yahoo.com> wrote:

 On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz>  
 wrote:

 Only if the compiler prefers opApply to range methods, does it?
It should. If it doesn't, that is a bug. The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply.
Thanks. So, if we had this support which I am asking for: foreach(index, value; range) { } And, if someone adds opApply to that range, with a different type for the first variable then an existing foreach (using index, value) is likely to stop compiling due to type problems. This seems acceptable to me.
I think any type that does both opApply and range iteration is asking for problems :) D has a nasty way of choosing "all or nothing" for overloads, meaning it may decide "this is a range" or "this is opApply", but if you have both, it picks one or the other. I'd rather see it do: 1. can I satisfy this foreach using opApply? If yes, do it. 2. If not, can I satisfy this foreach using range iteration? This may be how it works, I honestly don't know.
 There is an outside chance it might keep on compiling, like if 'i' is  
 not used in a strongly typed way, i.e. passed to a writefln or  
 similar.  In this case we have silently changed behaviour.

 Is this acceptable?
Adding opApply is changing the API of the range. If the range does something different based on whether you use the range interface or opApply, then this is a logic error IMO. The easiest thing is to just not use opApply and range primitives together :) One separation I like to use in my code is that you use opApply on a container, but range primitives on a range for that container. And a container is not a range.
Makes sense to me. :) R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
prev sibling parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Steven Schveighoffer"  wrote in message 
news:op.xbmyjnnzeav7ka stevens-macbook-pro.local...

 I'd rather see it do:

 1. can I satisfy this foreach using opApply? If yes, do it.
 2. If not, can I satisfy this foreach using range iteration?

 This may be how it works, I honestly don't know.
It is.
Feb 21 2014
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 21 Feb 2014 09:58:58 -0500, Daniel Murphy  
<yebbliesnospam gmail.com> wrote:

 "Steven Schveighoffer"  wrote in message  
 news:op.xbmyjnnzeav7ka stevens-macbook-pro.local...

 I'd rather see it do:

 1. can I satisfy this foreach using opApply? If yes, do it.
 2. If not, can I satisfy this foreach using range iteration?

 This may be how it works, I honestly don't know.
It is.
Good, thank you for checking! -Steve
Feb 21 2014
prev sibling parent reply Justin Whear <justin economicmodeling.com> writes:
On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
 
 More importantly, this gets in the way of behaviour which may be
 desirable later, foreach being able to unpack tuples from ranges.
 I would like if it was possible to return Tuple!(A, B) from front() and
 write foreach(a, b; range) to interate through those thing, unpacking
 the values with an alias, so this...
 
 foreach(a, b; range) {
 }
 
 ... could rewrite to roughly this. (There may be a better way.)
 
 foreach(_someInternalName; range) {
      alias a = _someInternalName[0];
      alias b = _someInternalName[1];
 }
Tuple unpacking already works in foreach. This code has compiled since at least 2.063.2: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3)); // unpack the string into `s`, the integer into `i` foreach (s, i; tuples) writeln(s, ", ", i); }
Feb 20 2014
next sibling parent reply "w0rp" <devw0rp gmail.com> writes:
On Thursday, 20 February 2014 at 16:30:42 UTC, Justin Whear wrote:

 Tuple unpacking already works in foreach.  This code has 
 compiled since
 at least 2.063.2:

 import std.stdio;
 import std.range;
 void main(string[] args)
 {
     auto tuples = ["a", "b", "c"].zip(iota(0, 3));

     // unpack the string into `s`, the integer into `i`
     foreach (s, i; tuples)
         writeln(s, ", ", i);
 }
I did not know that. When did that happen? It didn't appear in any changelogs and it works when I tried it in 2.064 on my machine too. I suppose the next step after that would be to support nested unpacking, but that would require a change in syntax so it would be much more complicated.
Feb 20 2014
next sibling parent Justin Whear <justin economicmodeling.com> writes:
On Thu, 20 Feb 2014 19:34:17 +0000, w0rp wrote:

 On Thursday, 20 February 2014 at 16:30:42 UTC, Justin Whear wrote:
 
 Tuple unpacking already works in foreach.  This code has compiled since
 at least 2.063.2:

 import std.stdio;
 import std.range;
 void main(string[] args)
 {
     auto tuples = ["a", "b", "c"].zip(iota(0, 3));

     // unpack the string into `s`, the integer into `i`
     foreach (s, i; tuples)
         writeln(s, ", ", i);
 }
I did not know that. When did that happen? It didn't appear in any changelogs and it works when I tried it in 2.064 on my machine too. I suppose the next step after that would be to support nested unpacking, but that would require a change in syntax so it would be much more complicated.
January 24th, 2012: http://forum.dlang.org/thread/ mailman.756.1327362275.16222.digitalmars-d puremagic.com#post- mailman.757.1327365651.16222.digitalmars-d:40puremagic.com That said, it is not documented, see this bug: http://d.puremagic.com/ issues/show_bug.cgi?id=7361
Feb 20 2014
prev sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 20 February 2014 at 19:34:17 UTC, w0rp wrote:
  I suppose the next step after that would be to
 support nested unpacking, but that would require a change in
 syntax so it would be much more complicated.
You mean this? void main() { import std.typecons : tuple; import std.range : repeat; foreach(k,v1, v2; tuple(1, tuple(2, 3)).repeat(4)) {} } Yeah, that works.
Feb 21 2014
parent "w0rp" <devw0rp gmail.com> writes:
On Saturday, 22 February 2014 at 03:51:19 UTC, Jesse Phillips 
wrote:
 On Thursday, 20 February 2014 at 19:34:17 UTC, w0rp wrote:
 I suppose the next step after that would be to
 support nested unpacking, but that would require a change in
 syntax so it would be much more complicated.
You mean this? void main() { import std.typecons : tuple; import std.range : repeat; foreach(k,v1, v2; tuple(1, tuple(2, 3)).repeat(4)) {} } Yeah, that works.
Ah, I didn't realise you could decompose tuples that way. I suppose this in Python... l = [((1, 2), (3, 4, 5))] for (x, y), (a, b, c) in l: print (x, y, a, b, c) ... kind of becomes this in D. auto left = tuple(1, 2); auto right = tuple(3, 4, 5); auto both = tuple(left, right); auto l = [both]; // identity map function to trick the array into being just a range. foreach(x, y, a, b, c; l.map!(x => x)) { writeln(x, y, a, b, c); } Then static typing makes you not worry about which thing comes from which tuple.
Feb 25 2014
prev sibling parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Thu, 20 Feb 2014 16:30:42 -0000, Justin Whear  
<justin economicmodeling.com> wrote:

 On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
 More importantly, this gets in the way of behaviour which may be
 desirable later, foreach being able to unpack tuples from ranges.
 I would like if it was possible to return Tuple!(A, B) from front() and
 write foreach(a, b; range) to interate through those thing, unpacking
 the values with an alias, so this...

 foreach(a, b; range) {
 }

 ... could rewrite to roughly this. (There may be a better way.)

 foreach(_someInternalName; range) {
      alias a = _someInternalName[0];
      alias b = _someInternalName[1];
 }
Tuple unpacking already works in foreach. This code has compiled since at least 2.063.2: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3)); // unpack the string into `s`, the integer into `i` foreach (s, i; tuples) writeln(s, ", ", i); }
Does this work for more than 2 values? Can the first value be something other than an integer? R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
next sibling parent "Regan Heath" <regan netmail.co.nz> writes:
On Fri, 21 Feb 2014 10:02:43 -0000, Regan Heath <regan netmail.co.nz>  
wrote:

 On Thu, 20 Feb 2014 16:30:42 -0000, Justin Whear  
 <justin economicmodeling.com> wrote:

 On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
 More importantly, this gets in the way of behaviour which may be
 desirable later, foreach being able to unpack tuples from ranges.
 I would like if it was possible to return Tuple!(A, B) from front() and
 write foreach(a, b; range) to interate through those thing, unpacking
 the values with an alias, so this...

 foreach(a, b; range) {
 }

 ... could rewrite to roughly this. (There may be a better way.)

 foreach(_someInternalName; range) {
      alias a = _someInternalName[0];
      alias b = _someInternalName[1];
 }
Tuple unpacking already works in foreach. This code has compiled since at least 2.063.2: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3)); // unpack the string into `s`, the integer into `i` foreach (s, i; tuples) writeln(s, ", ", i); }
Does this work for more than 2 values? Can the first value be something other than an integer?
Answered this myself. What is supported is: foreach(key, value; tuple) { } But, what is not supported is more than 2 values. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
prev sibling parent reply Justin Whear <justin economicmodeling.com> writes:
On Fri, 21 Feb 2014 10:02:43 +0000, Regan Heath wrote:

 On Thu, 20 Feb 2014 16:30:42 -0000, Justin Whear
 <justin economicmodeling.com> wrote:
 
 On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
 More importantly, this gets in the way of behaviour which may be
 desirable later, foreach being able to unpack tuples from ranges.
 I would like if it was possible to return Tuple!(A, B) from front()
 and write foreach(a, b; range) to interate through those thing,
 unpacking the values with an alias, so this...

 foreach(a, b; range) {
 }

 ... could rewrite to roughly this. (There may be a better way.)

 foreach(_someInternalName; range) {
      alias a = _someInternalName[0];
      alias b = _someInternalName[1];
 }
Tuple unpacking already works in foreach. This code has compiled since at least 2.063.2: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3)); // unpack the string into `s`, the integer into `i` foreach (s, i; tuples) writeln(s, ", ", i); }
Does this work for more than 2 values? Can the first value be something other than an integer? R
Yes to both questions. In the following example I use a four element tuple, the first element of which is a string: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3), [1.2, 2.3, 3.4], ['x', 'y', 'z']); foreach (s, i, f, c; tuples) writeln(s, ", ", i, ", ", f, ", ", c); } Compiles with dmd 2.063.2
Feb 21 2014
parent "Regan Heath" <regan netmail.co.nz> writes:
On Fri, 21 Feb 2014 16:59:26 -0000, Justin Whear  
<justin economicmodeling.com> wrote:

 On Fri, 21 Feb 2014 10:02:43 +0000, Regan Heath wrote:

 On Thu, 20 Feb 2014 16:30:42 -0000, Justin Whear
 <justin economicmodeling.com> wrote:

 On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
 More importantly, this gets in the way of behaviour which may be
 desirable later, foreach being able to unpack tuples from ranges.
 I would like if it was possible to return Tuple!(A, B) from front()
 and write foreach(a, b; range) to interate through those thing,
 unpacking the values with an alias, so this...

 foreach(a, b; range) {
 }

 ... could rewrite to roughly this. (There may be a better way.)

 foreach(_someInternalName; range) {
      alias a = _someInternalName[0];
      alias b = _someInternalName[1];
 }
Tuple unpacking already works in foreach. This code has compiled since at least 2.063.2: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3)); // unpack the string into `s`, the integer into `i` foreach (s, i; tuples) writeln(s, ", ", i); }
Does this work for more than 2 values? Can the first value be something other than an integer? R
Yes to both questions. In the following example I use a four element tuple, the first element of which is a string: import std.stdio; import std.range; void main(string[] args) { auto tuples = ["a", "b", "c"].zip(iota(0, 3), [1.2, 2.3, 3.4], ['x', 'y', 'z']); foreach (s, i, f, c; tuples) writeln(s, ", ", i, ", ", f, ", ", c); } Compiles with dmd 2.063.2
Thanks. I understand this now, I had forgotten about tuple unpacking/flattening. DMD supports at least 4 distinct types of foreach. The range foreach is the one which I want an index/count added to, and this change will have no effect on the tuple case shown above. It should "just work", and there is no good reason not to make it so. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 24 2014
prev sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 20 February 2014 at 11:15:14 UTC, Regan Heath wrote:
 I am posting this again because I didn't get any feedback on my 
 idea, which may be TL;DR or because people think it's a dumb 
 idea and they were politely ignoring it :p
I certainly have wanted counts of the range iteration, but I do believe it becomes too complex to support and that even if we state 'i' is to represent a count and not an index, people will still want an index and expect it to be an index of their original range even though it makes no possible sense from the perspective of iterating over a different range from the original. I also don't find myself needing to count iterations very often, and I believe when I do, it is because I want to use that count as an index (possibly needing to add to some global count, but I don't need it enough to remember).
 Scheme 1)
As Marc said, "ails backwards-compatibility." A change like this will never exist if it isn't backwards compatible. There are very few changes which will be accepted if backwards compatibility isn't preserved.
 Scheme 2)
 However, if a type is given and the type can be unambiguously 
 matched to a single tuple component then do so.

 double[string] AA;
 foreach (string k; AA) {} // k is "key"
While probably not common, what if one needed to switch key/value string[double] AA; or something similar, the type system no longer helps. But again, this seems pretty much uneventful.
 foreach (i, k, v; AA.byPairs.enumerate) {}
 foreach (i, k, v; AA) {} // better
Bringing this back to range iteration: foreach(i, v1, v2; tuple(0,1).repeat(10)) writeln(i, "\t",v1, "\t",v2); Later the range gets a new value, the foreach would still compile but be wrong: foreach(i, v1, v2; tuple(0,1,2).repeat(10)) writeln(i, "\t",v1, "\t",v2); With enumerate, there is an error. foreach(i, v1, v2; tuple(0,1,2).repeat(10).enumerate) writeln(i, "\t", v1, "\t", v2); Error: cannot infer argument types I don't see enough benefit for making this a language feature. foreach(i, v1, v2; tuple(0,1).repeat(10).enumerate) writeln(i, "\t", v1, "\t", v2); This works today! And once enumerate is part of Phobos it will just need an import std.range to use it.
Feb 20 2014
next sibling parent "w0rp" <devw0rp gmail.com> writes:
On Friday, 21 February 2014 at 02:34:30 UTC, Jesse Phillips wrote:
 I don't see enough benefit for making this a language feature.


     foreach(i, v1, v2; tuple(0,1).repeat(10).enumerate)
         writeln(i, "\t", v1, "\t", v2);

 This works today! And once enumerate is part of Phobos it will 
 just need an import std.range to use it.
This.
Feb 21 2014
prev sibling parent reply "Regan Heath" <regan netmail.co.nz> writes:
My current thinking:

  - I still think adding index to range foreach is a good idea.



  - enumerate is not as flexible as many people seem to think.


On Fri, 21 Feb 2014 02:34:28 -0000, Jesse Phillips  
<Jesse.K.Phillips+D gmail.com> wrote:

 On Thursday, 20 February 2014 at 11:15:14 UTC, Regan Heath wrote:
 I am posting this again because I didn't get any feedback on my idea,  
 which may be TL;DR or because people think it's a dumb idea and they  
 were politely ignoring it :p
I certainly have wanted counts of the range iteration, but I do believe it becomes too complex to support and that even if we state 'i' is to represent a count and not an index, people will still want an index and expect it to be an index of their original range even though it makes no possible sense from the perspective of iterating over a different range from the original.
I don't understand how this is "complex to support"? It's simple. It's a count, not an index unless the range is indexable. If people are going to expect an index here, they will expect one with enumerate as well - and are going to be equally disappointed. So, they need to be aware of this regardless.
 I also don't find myself needing to count iterations very often, and I  
 believe when I do, it is because I want to use that count as an index  
 (possibly needing to add to some global count, but I don't need it  
 enough to remember).
The justification for this change is the same as for enumerate. It is common enough to make it important, and when it happens it's frustrating enough that it needs fixing. My specific example didn't want an index, or rather it wanted an index into the result set which I believe is just as common if not more common than wanting an index into the source - especially given that they are often the same thing. For example, I find myself using an index to control loop behaviour, most often for detecting the first and last iterations than anything else. A counter will let you do that just as well as an index.
 Scheme 1)
As Marc said, "ails backwards-compatibility." A change like this will never exist if it isn't backwards compatible. There are very few changes which will be accepted if backwards compatibility isn't preserved.
Sure. I personally find this idea compelling enough to warrant some breakage, it is simple, powerful and extensible and avoids all the issues of optional indexes with tuple expansion. But, I can see how someone might disagree.
 Scheme 2)
 However, if a type is given and the type can be unambiguously matched  
 to a single tuple component then do so.

 double[string] AA;
 foreach (string k; AA) {} // k is "key"
While probably not common, what if one needed to switch key/value string[double] AA; or something similar, the type system no longer helps. But again, this seems pretty much uneventful.
Perhaps I wasn't clear, this would work fine: string[double] AA; foreach (string v; AA) {} // v is "value" foreach (double k; AA) {} // k is "key" or am I missing the point you're making?
 foreach (i, k, v; AA.byPairs.enumerate) {}
 foreach (i, k, v; AA) {} // better
Bringing this back to range iteration: foreach(i, v1, v2; tuple(0,1).repeat(10)) writeln(i, "\t",v1, "\t",v2); Later the range gets a new value, the foreach would still compile but be wrong: foreach(i, v1, v2; tuple(0,1,2).repeat(10)) writeln(i, "\t",v1, "\t",v2); With enumerate, there is an error. foreach(i, v1, v2; tuple(0,1,2).repeat(10).enumerate) writeln(i, "\t", v1, "\t", v2); Error: cannot infer argument types
Sure, this is an issue with having the optional index/count variable, which is not something foreach with enumerate allows. This is another
      foreach(i, v1, v2; tuple(0,1).repeat(10).enumerate)
          writeln(i, "\t", v1, "\t", v2);

 This works today! And once enumerate is part of Phobos it will just need  
 an import std.range to use it.
I don't believe this works today. My understanding of what is currently supported is.. foreach(index, value; array) { } foreach(value; range) { } // no support for index/count foreach(key, value; tuple) { } // no support for index/count And, my understanding of enumerate is that it simply creates a tuple from an index and a range value, taking it from the range foreach case above, to the tuple foreach case. This is not extensible to more than 2 values. In fact, it's pretty limited until we get full built-in tuple expansion support. To test this understanding I pulled down the source for enumerate and coded this up: import std.stdio; import std.range; import std.typecons; ..paste enumerate here.. // line 5 void main() { foreach(i, v1, v2; tuple(0,1,2).repeat(10).enumerate) // line 124 writeln(i, "\t", v1, "\t", v2); } I get the following errors: testtup.d(124): Error: template testtup.enumerate does not match any function template declaration. Candidates are: testtup.d(5): testtup.enumerate(Range)(Range range, size_t start = 0) if(isInputRange!Range && (!hasLength!Range || is(Largest!(size_t, typeof(range.length)) == size_t))) testtup.d(124): Error: template testtup.enumerate(Range)(Range range, size_t start = 0) if (isInputRange!Range && (!hasLength!Range || is(Largest!(size_t, typeof(range.length)) == size_t))) cannot deduce template function from argument types !()(Take!(Repeat!(Tuple!(int, int, int)))) I believe this errors because tuple foreach only allows (value) or (key, value) not (index, key, value) and certainly not (index, value, value, value) which is what we're trying to do here. Am I right? If so, and given that we currently support: foreach(index, value; array) { } foreach(value; range) { } // no support for index/count foreach(key, value; tuple) { } // no support for index/count There is absolutely no good reason not to add: foreach(index, value; range) { } It is backwards compatible and breaks no code, nor does it interfere with tuple expansion because that is a separate foreach case. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 21 February 2014 at 11:12:54 UTC, Regan Heath wrote:
  - enumerate is not as flexible as many people seem to think.
Only seeing the enumerate missing the ability to optionally add an index, but if you aren't adding an index you don't need enumerate.
 On Fri, 21 Feb 2014 02:34:28 -0000, Jesse Phillips 
 <Jesse.K.Phillips+D gmail.com> wrote:
 I don't understand how this is "complex to support"?  It's 
 simple.  It's a count, not an index unless the range is 
 indexable.  If people are going to expect an index here, they 
 will expect one with enumerate as well - and are going to be 
 equally disappointed.  So, they need to be aware of this 
 regardless.
You've provided 3 schemes to support this feature. This suggest there are several "right" ways to bring this into the language, while you prefer 1 someone may prefer 3. At least with enumerate one will need to go to the documentation which explains enumerate doesn't provide an index... I haven't actually reviewed the docs.
 I also don't find myself needing to count iterations very 
 often, and I believe when I do, it is because I want to use 
 that count as an index (possibly needing to add to some global 
 count, but I don't need it enough to remember).
The justification for this change is the same as for enumerate. It is common enough to make it important, and when it happens it's frustrating enough that it needs fixing.
I disagree. Enumerate is satisfactory (since it isn't in Phobos I can see it as frustrating).
 For example, I find myself using an index to control loop 
 behaviour, most often for detecting the first and last 
 iterations than anything else.  A counter will let you do that 
 just as well as an index.
I wonder if there is a change to the algorithm which would allow you to not need the first/last iteration. I think this is the main reason I don't need a count, I've learned different ways to solve a problem. Which is beneficial since it leads to chaining functions instead of relying on foreach.
 Sure.  I personally find this idea compelling enough to warrant 
 some breakage, it is simple, powerful and extensible and avoids 
 all the issues of optional indexes with tuple expansion.  But, 
 I can see how someone might disagree.
Yes, I understand. But D is at a stage in its life when not every little detail can be polished. Believe me, D has other areas which need polishing but can't be.
 string[double] AA;

 or something similar, the type system no longer helps. But 
 again, this seems pretty much uneventful.
Perhaps I wasn't clear, this would work fine: string[double] AA; foreach (string v; AA) {} // v is "value" foreach (double k; AA) {} // k is "key" or am I missing the point you're making?
if AA is changed to a double[string], then your value loop iterates on keys and your key loop iterates on values.
     foreach(i, v1, v2; tuple(0,1).repeat(10).enumerate)
         writeln(i, "\t", v1, "\t", v2);

 This works today! And once enumerate is part of Phobos it will 
 just need an import std.range to use it.
I tested all my claims about enumerate. You need it to import std.traits or else is(Largest(...)) will always be false.
Feb 21 2014
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Fri, 21 Feb 2014 15:35:44 -0000, Jesse Phillips  
<Jesse.K.Phillips+D gmail.com> wrote:
 You've provided 3 schemes to support this feature. This suggest there  
 are several "right" ways to bring this into the language, while you  
 prefer 1 someone may prefer 3.
Ignore the 3 schemes they were just me thinking about how what I actually want will affect built in tuple expansion etc. I want just 1 thing to change (at this time), an index added to foreach over ranges so that it matches arrays, e.g. foreach(index, value; range) { } The code change is likely quite literally just adding an int to the foreach handler for ranges, passing it to the foreach body, and incrementing it afterwards. That's it, well, plus the front end code to bind the variable. All I am suggesting is that we take what we currently have: foreach([index, ]value; array) { } foreach(value; range) { } foreach(key, value; tuple) { } and make this possible too: foreach([index, ]value; range) { }
 string[double] AA;

 or something similar, the type system no longer helps. But again, this  
 seems pretty much uneventful.
Perhaps I wasn't clear, this would work fine: string[double] AA; foreach (string v; AA) {} // v is "value" foreach (double k; AA) {} // k is "key" or am I missing the point you're making?
if AA is changed to a double[string], then your value loop iterates on keys and your key loop iterates on values.
No, I was actually suggesting a change here, the compiler would use type matching not ordering to assign the variables. So because 'v' is a string, it is bound to the value not the key.
     foreach(i, v1, v2; tuple(0,1).repeat(10).enumerate)
         writeln(i, "\t", v1, "\t", v2);

 This works today! And once enumerate is part of Phobos it will just  
 need an import std.range to use it.
I tested all my claims about enumerate. You need it to import std.traits or else is(Largest(...)) will always be false.
Thanks! Ok, so how is this working? ahh, ok I think I get it. enumerate returns a range, whose values are Tuples of index/value where value is also a tuple so is flattened, and then the whole lot is flattened into the foreach. So, while the range foreach only supports: foreach(value; range) { } value in this case is a flattened tuple of (index, v1, v2, ...) Yes? I had completely forgotten about tuple flattening. I don't think this affects what I actually want to change, we can have: foreach(index, value; range) { } and still flatten tuples into value, you would simply have to provide one extra variable to get an index. Make sense? R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 21 2014
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Friday, 21 February 2014 at 16:41:00 UTC, Regan Heath wrote:
 and make this possible too:

 	foreach([index, ]value; range) { }
I understand the user interface is simple, but you created 3 statements about how it could be achieved and work/not work with the existing setup. Each have their positives and negatives, it would not make sense to "just choose one" and hope it all works out.
 if AA is changed to a double[string], then your value loop 
 iterates on keys and your key loop iterates on values.
No, I was actually suggesting a change here, the compiler would use type matching not ordering to assign the variables. So because 'v' is a string, it is bound to the value not the key.
And string is the key, double[string] is not the same as string[double]. Also string[string], ambiguous yet common. There are many things to consider when adding a feature, it is not good to ignore what can go wrong.
 Thanks!  Ok, so how is this working?  ahh, ok I think I get it.
  enumerate returns a range, whose values are Tuples of 
 index/value where value is also a tuple so is flattened, and 
 then the whole lot is flattened into the foreach.
Sounds like you understand it, seams foreach will flatten all tuples.
 I don't think this affects what I actually want to change, we 
 can have:

 	foreach(index, value; range) { }

 and still flatten tuples into value, you would simply have to 
 provide one extra variable to get an index.

 Make sense?
Yes, but I'm saying we don't need it because foreach(index, value; range.enumerate) { } is good enough. Not perfect, but good enough.
Feb 21 2014
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Fri, 21 Feb 2014 19:42:41 -0000, Jesse Phillips  
<Jesse.K.Phillips+D gmail.com> wrote:
 On Friday, 21 February 2014 at 16:41:00 UTC, Regan Heath wrote:
 and make this possible too:

 	foreach([index, ]value; range) { }
I understand the user interface is simple, but you created 3 statements about how it could be achieved and work/not work with the existing setup. Each have their positives and negatives, it would not make sense to "just choose one" and hope it all works out.
 if AA is changed to a double[string], then your value loop iterates on  
 keys and your key loop iterates on values.
No, I was actually suggesting a change here, the compiler would use type matching not ordering to assign the variables. So because 'v' is a string, it is bound to the value not the key.
And string is the key, double[string] is not the same as string[double]. Also string[string], ambiguous yet common. There are many things to consider when adding a feature, it is not good to ignore what can go wrong.
Yes.. something is not being communicated here. I addressed all this in the OP.
 Thanks!  Ok, so how is this working?  ahh, ok I think I get it.
  enumerate returns a range, whose values are Tuples of index/value  
 where value is also a tuple so is flattened, and then the whole lot is  
 flattened into the foreach.
Sounds like you understand it, seams foreach will flatten all tuples.
 I don't think this affects what I actually want to change, we can have:

 	foreach(index, value; range) { }

 and still flatten tuples into value, you would simply have to provide  
 one extra variable to get an index.

 Make sense?
Yes, but I'm saying we don't need it because foreach(index, value; range.enumerate) { } is good enough. Not perfect, but good enough.
No, not good enough. This should just work, there is no good reason for it not to. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 24 2014
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Monday, 24 February 2014 at 10:29:46 UTC, Regan Heath wrote:
 No, not good enough.  This should just work, there is no good 
 reason for it not to.

 R
I have long since given up believing this should be in the language, I'm satisfied with the reasons I gave for why it is not in the language and why it is not needed to be in the language. You asked for feedback, I've given mine to you. I'm ok with you disagreeing with that.
Feb 24 2014
parent reply "Regan Heath" <regan netmail.co.nz> writes:
On Mon, 24 Feb 2014 17:58:51 -0000, Jesse Phillips  
<Jesse.K.Phillips+D gmail.com> wrote:

 On Monday, 24 February 2014 at 10:29:46 UTC, Regan Heath wrote:
 No, not good enough.  This should just work, there is no good reason  
 for it not to.

 R
I have long since given up believing this should be in the language, I'm satisfied with the reasons I gave for why it is not in the language and why it is not needed to be in the language. You asked for feedback, I've given mine to you. I'm ok with you disagreeing with that.
Sure, no worries. :) I'd just like to list your objections here and respond to them all, in one place, without the distracting issues surrounding the 3 extra schemes I mentioned. Can you please correct me if I miss represent you in any way. 1. Adding 'i' on ranges is not necessarily an index and people will expect an index. 2. You don't need to count iterations very often. 3. Your point about the "range gets a new value and foreach would compile but be wrong" 4. This area of D is not important enough to polish 5. We will have enumerate soon, and won't need it. I think this is every point you made in opposition of the change I want (excluding those in opposition of the 3 additional schemes - which in hindsight I should just have left off) flattened tuple foreach, not the range foreach I want to change. Making the change I want will have no effect on the given example. type more rather than less? For my point of view, it seems an obvious lack in D that the range foreach doesn't have the same basic functionality as the array foreach. That's pretty much my whole argument, it should just work in the same way as arrays. But, as you say we're free to disagree here, I was just about to suggest we were at an impasse myself. R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Feb 25 2014
parent "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Tuesday, 25 February 2014 at 17:34:25 UTC, Regan Heath wrote:
 On Mon, 24 Feb 2014 17:58:51 -0000, Jesse Phillips 
 <Jesse.K.Phillips+D gmail.com> wrote:

 On Monday, 24 February 2014 at 10:29:46 UTC, Regan Heath wrote:
 No, not good enough.  This should just work, there is no good 
 reason for it not to.

 R
I have long since given up believing this should be in the language, I'm satisfied with the reasons I gave for why it is not in the language and why it is not needed to be in the language. You asked for feedback, I've given mine to you. I'm ok with you disagreeing with that.
Sure, no worries. :) I'd just like to list your objections here and respond to them all, in one place, without the distracting issues surrounding the 3 extra schemes I mentioned. Can you please correct me if I miss represent you in any way. 1. Adding 'i' on ranges is not necessarily an index and people will expect an index. 2. You don't need to count iterations very often. 3. Your point about the "range gets a new value and foreach would compile but be wrong" 4. This area of D is not important enough to polish 5. We will have enumerate soon, and won't need it.
Seems right, though 4 is, to include this behavior would be a breaking change, and the polish on this isn't worth it. Many of your examples use AA and provide examples where their iteration has a "count" variable. I have never wanted to do this, and frankly would be extremely concerned for what the code would be doing with it. AA are unordered, ranges tend to be ordered, but this isn't a requirement.

 given is a flattened tuple foreach, not the range foreach I 
 want to change.  Making the change I want will have no effect 
 on the given example.
I think the examples you are referring to are specific to the foreach you desire, but use enumerate to show the current behavior. It was not to claim that your addition would break tuple flattening.


 would you "want" to type more rather than less?
And that is awesome. Who needs this lack of typing."
 For my point of view, it seems an obvious lack in D that the 
 range foreach doesn't have the same basic functionality as the 
 array foreach.  That's pretty much my whole argument, it should 
 just work in the same way as arrays.
Ranges aren't always sequential. It doesn't make sense on many types of ranges (random, associative array if it provided a range). Arrays on the other hand are sequential by definition.
Feb 25 2014