Repost: make foreach(i, a; range) "just work"

Regan Heath regan at netmail.co.nz
Fri Feb 21 03:12:52 PST 2014


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 -0000, Jesse Phillips  
<Jesse.K.Phillips+D at gmail.com> wrote:

> On Thursday, 20 February 2014 at 11:15:14 UTC, Regan Heath wrote:
>> I am posting this again because I didn't get any feedback on my idea,  
>> which may be TL;DR or because people think it's a dumb idea and they  
>> were politely ignoring it :p
>
> I certainly have wanted counts of the range iteration, but I do believe  
> it becomes too complex to support and that even if we state 'i' is to  
> represent a count and not an index, people will still want an index and  
> expect it to be an index of their original range even though it makes no  
> possible sense from the perspective of iterating over a different range  
> from the original.

I don't understand how this is "complex to support"?  It's simple.  It's a  
count, not an index unless the range is indexable.  If people are going to  
expect an index here, they will expect one with enumerate as well - and  
are going to be equally disappointed.  So, they need to be aware of this  
regardless.


> I also don't find myself needing to count iterations very often, and I  
> believe when I do, it is because I want to use that count as an index  
> (possibly needing to add to some global count, but I don't need it  
> enough to remember).

The justification for this change is the same as for enumerate.

It is common enough to make it important, and when it happens it's  
frustrating enough that it needs fixing.

My specific example didn't want an index, or rather it wanted an index  
into the result set which I believe is just as common if not more common  
than wanting an index into the source - especially given that they are  
often the same thing.

For example, I find myself using an index to control loop behaviour, most  
often for detecting the first and last iterations than anything else.  A  
counter will let you do that just as well as an index.


>> Scheme 1)
>
> As Marc said, "ails backwards-compatibility." A change like this will  
> never exist if it isn't backwards compatible. There are very few changes  
> which will be accepted if backwards compatibility isn't preserved.

Sure.  I personally find this idea compelling enough to warrant some  
breakage, it is simple, powerful and extensible and avoids all the issues  
of optional indexes with tuple expansion.  But, I can see how someone  
might disagree.


>> Scheme 2)
>> However, if a type is given and the type can be unambiguously matched  
>> to a single tuple component then do so.
>>
>> double[string] AA;
>> foreach (string k; AA) {} // k is "key"
>
> While probably not common, what if one needed to switch key/value
>
> string[double] AA;
>
> or something similar, the type system no longer helps. But again, this  
> seems pretty much uneventful.

Perhaps I wasn't clear, this would work fine:

string[double] AA;
foreach (string v; AA) {} // v is "value"
foreach (double k; AA) {} // k is "key"

or am I missing the point you're making?


>> foreach (i, k, v; AA.byPairs.enumerate) {}
>> foreach (i, k, v; AA) {} // better
>
> Bringing this back to range iteration:
>
>      foreach(i, v1, v2; tuple(0,1).repeat(10))
>          writeln(i, "\t",v1, "\t",v2);
>
> Later the range gets a new value, the foreach would still compile but be  
> wrong:
>
>      foreach(i, v1, v2; tuple(0,1,2).repeat(10))
>          writeln(i, "\t",v1, "\t",v2);
>
> With enumerate, there is an error.
>
>      foreach(i, v1, v2; tuple(0,1,2).repeat(10).enumerate)
>          writeln(i, "\t", v1, "\t", v2);
> Error: cannot infer argument types

Sure, this is an issue with having the optional index/count variable,  
which is not something foreach with enumerate allows.  This is another  
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()
{
	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/


More information about the Digitalmars-d mailing list