Repost: make foreach(i, a; range) "just work"
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 Lets look at bearophile's examples re-written for scheme #1 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 err
Re: Repost: make foreach(i, a; range) "just work"
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. Scheme #1 fails backwards-compatibility. Scheme #2 doesn't, but I feel the matching rules if a type is 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); ...) (Scheme #3 seems just too complicated for my taste. It's important to be intuitively understandable and predictable.)
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 12:56:27 -, Marc Schütz wrote: 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. Scheme #1 fails backwards-compatibility. Fair enough. We can always pack things manually using something like the enumerate() method mentioned in the link. Scheme #2 doesn't, but I feel the matching rules if a type is 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); ...) Cool. (Scheme #3 seems just too complicated for my taste. It's important to 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/
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 13:04:55 -, w0rp 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(; 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. :) R -- Using Opera's revolutionary email client: http://www.opera.com/mail/
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 13:04:55 +, 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); }
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath 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
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 19:34:17 +, 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.digitalmar...@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
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 16:30:42 -, Justin Whear wrote: On Thu, 20 Feb 2014 13:04:55 +, 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/
Re: Repost: make foreach(i, a; range) "just work"
My current thinking: - I still think adding index to range foreach is a good idea. - I realise that scheme #2 isn't workable. - I still like scheme #1 over tuple expansion as it avoids all the issues which make scheme #2 unworkable. - enumerate is not as flexible as many people seem to think. On Fri, 21 Feb 2014 02:34:28 -, Jesse Phillips 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 reason I prefer scheme #1, you never have this issue no matter what. 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() { forea
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 10:02:43 -, Regan Heath wrote: On Thu, 20 Feb 2014 16:30:42 -, Justin Whear wrote: On Thu, 20 Feb 2014 13:04:55 +, 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/
Re: Repost: make foreach(i, a; range) "just work"
On Thu, 20 Feb 2014 17:09:31 -, Steven Schveighoffer wrote: On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath 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/
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 06:21:39 -0500, Regan Heath wrote: On Thu, 20 Feb 2014 17:09:31 -, Steven Schveighoffer wrote: On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath 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
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 14:29:37 -, Steven Schveighoffer wrote: On Fri, 21 Feb 2014 06:21:39 -0500, Regan Heath wrote: On Thu, 20 Feb 2014 17:09:31 -, Steven Schveighoffer wrote: On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath 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/
Re: Repost: make foreach(i, a; range) "just work"
"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.
Re: Repost: make foreach(i, a; range) "just work"
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 -, Jesse Phillips 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.
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 15:35:44 -, Jesse Phillips 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/
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 09:58:58 -0500, Daniel Murphy 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
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 10:02:43 +, Regan Heath wrote: > On Thu, 20 Feb 2014 16:30:42 -, Justin Whear > wrote: > >> On Thu, 20 Feb 2014 13:04:55 +, 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
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 19:42:41 -, Jesse Phillips 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/
Re: Repost: make foreach(i, a; range) "just work"
On Fri, 21 Feb 2014 16:59:26 -, Justin Whear wrote: On Fri, 21 Feb 2014 10:02:43 +, Regan Heath wrote: On Thu, 20 Feb 2014 16:30:42 -, Justin Whear wrote: On Thu, 20 Feb 2014 13:04:55 +, 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/
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
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.
Re: Repost: make foreach(i, a; range) "just work"
On Mon, 24 Feb 2014 17:58:51 -, Jesse Phillips 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) I believe objection #3 is invalid. The foreach in the example 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 strongest objection here is #1, #2 and #4 are fairly subjective and #5 just seems a little odd to me, why would you "want" to 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/
Re: Repost: make foreach(i, a; range) "just work"
On Tuesday, 25 February 2014 at 17:34:25 UTC, Regan Heath wrote: On Mon, 24 Feb 2014 17:58:51 -, Jesse Phillips 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. I believe objection #3 is invalid. The foreach in the example 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. I think the strongest objection here is #1, #2 and #4 are fairly subjective and #5 just seems a little odd to me, why would you "want" to type more rather than less? Yes, #5 comes down to "OMG you'll be able to type lots more text! 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.