digitalmars.D - Repost: make foreach(i, a; range) "just work"
- Regan Heath (187/195) Feb 20 2014 I am posting this again because I didn't get any feedback on my idea,
- "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> (20/20) Feb 20 2014 IMO, any change needs to be both backwards-compatible (i.e., it
- Regan Heath (18/37) Feb 20 2014 =
- w0rp (40/40) Feb 20 2014 I don't think this is a good idea. Say you have a class with
- w0rp (3/3) Feb 20 2014 I probably didn't do enough job of reading your post because it
- Regan Heath (11/18) Feb 20 2014 Which part? The initial solution to my initial problem, or one of the 3...
- Steven Schveighoffer (6/7) Feb 20 2014 It should. If it doesn't, that is a bug.
- Regan Heath (16/22) Feb 21 2014 Thanks.
- Steven Schveighoffer (18/40) Feb 21 2014 I think any type that does both opApply and range iteration is asking fo...
- Regan Heath (6/52) Feb 21 2014 Makes sense to me. :)
- Daniel Murphy (3/7) Feb 21 2014 It is.
- Steven Schveighoffer (4/13) Feb 21 2014 Good, thank you for checking!
- Justin Whear (12/28) Feb 20 2014 Tuple unpacking already works in foreach. This code has compiled since
- w0rp (6/18) Feb 20 2014 I did not know that. When did that happen? It didn't appear in
- Justin Whear (6/27) Feb 20 2014 January 24th, 2012: http://forum.dlang.org/thread/
- Jesse Phillips (9/12) Feb 21 2014 You mean this?
- w0rp (19/31) Feb 25 2014 Ah, I didn't realise you could decompose tuples that way.
- Regan Heath (7/35) Feb 21 2014 Does this work for more than 2 values? Can the first value be something...
- Regan Heath (8/43) Feb 21 2014 Answered this myself. What is supported is:
- Justin Whear (13/52) Feb 21 2014 Yes to both questions. In the following example I use a four element
- Regan Heath (10/62) Feb 24 2014 Thanks. I understand this now, I had forgotten about tuple
- Jesse Phillips (35/46) Feb 20 2014 I certainly have wanted counts of the range iteration, but I do
- w0rp (2/7) Feb 21 2014 This.
- Regan Heath (82/127) Feb 21 2014 My current thinking:
- Jesse Phillips (24/62) Feb 21 2014 Only seeing the enumerate missing the ability to optionally add
- Regan Heath (37/61) Feb 21 2014 Ignore the 3 schemes they were just me thinking about how what I actuall...
- Jesse Phillips (16/33) Feb 21 2014 I understand the user interface is simple, but you created 3
- Regan Heath (9/43) Feb 24 2014 Yes.. something is not being communicated here. I addressed all this in...
- Jesse Phillips (6/9) Feb 24 2014 I have long since given up believing this should be in the
- Regan Heath (31/41) Feb 25 2014 Sure, no worries. :)
- Jesse Phillips (17/55) Feb 25 2014 Seems right, though 4 is, to include this behavior would be a
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
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
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
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
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
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
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
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: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/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.
Feb 21 2014
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: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.On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz> wrote: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.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.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
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:Makes sense to me. :) R -- Using Opera's revolutionary email client: http://www.opera.com/mail/On Thu, 20 Feb 2014 17:09:31 -0000, Steven Schveighoffer <schveiguy yahoo.com> wrote: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.On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan netmail.co.nz> wrote: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.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.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.
Feb 21 2014
"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
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...Good, thank you for checking! -SteveI'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
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
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
On Thu, 20 Feb 2014 19:34:17 +0000, w0rp wrote:On Thursday, 20 February 2014 at 16:30:42 UTC, Justin Whear wrote: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=7361Tuple 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
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
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: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.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 25 2014
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: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/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 21 2014
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: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/On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:Does this work for more than 2 values? Can the first value be something other than an integer?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 21 2014
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: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.2On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:Does this work for more than 2 values? Can the first value be something other than an integer? RMore 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 21 2014
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: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/On Thu, 20 Feb 2014 16:30:42 -0000, Justin Whear <justin economicmodeling.com> wrote: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.2On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:Does this work for more than 2 values? Can the first value be something other than an integer? RMore 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 24 2014
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 :pI 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) {} // betterBringing 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
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
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 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 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 :pI 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).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.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 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.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?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.Sure, this is an issue with having the optional index/count variable, which is not something foreach with enumerate allows. This is anotherforeach (i, k, v; AA.byPairs.enumerate) {} foreach (i, k, v; AA) {} // betterBringing 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 typesforeach(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
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 disagree. Enumerate is satisfactory (since it isn't in Phobos I can see it as frustrating).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.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.if AA is changed to a double[string], then your value loop iterates on keys and your key loop iterates on values.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?I tested all my claims about enumerate. You need it to import std.traits or else is(Largest(...)) will always be false.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 21 2014
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) { }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.if AA is changed to a double[string], then your value loop iterates on keys and your key loop iterates on values.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?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/I tested all my claims about enumerate. You need it to import std.traits or else is(Largest(...)) will always be false.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 21 2014
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.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.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.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
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:Yes.. something is not being communicated here. I addressed all this in the OP.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.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.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.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/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 24 2014
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. RI 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
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: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/No, not good enough. This should just work, there is no good reason for it not to. RI 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 25 2014
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: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.On Monday, 24 February 2014 at 10:29:46 UTC, Regan Heath wrote: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.No, not good enough. This should just work, there is no good reason for it not to. RI 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.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