February 21, 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 :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.
February 21, 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.
February 21, 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:
>>
>> 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/
February 21, 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@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/
February 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:
>
>> On Thu, 20 Feb 2014 13:04:55 +0000, w0rp wrote:
>>>
>>> More importantly, this gets in the way of behaviour which may be
>>> desirable later, foreach being able to unpack tuples from ranges.
>>> I would like if it was possible to return Tuple!(A, B) from front() and
>>> write foreach(a, b; range) to interate through those thing, unpacking
>>> the values with an alias, so this...
>>>
>>> foreach(a, b; range) {
>>> }
>>>
>>> ... could rewrite to roughly this. (There may be a better way.)
>>>
>>> foreach(_someInternalName; range) {
>>>      alias a = _someInternalName[0];
>>>      alias b = _someInternalName[1];
>>> }
>>
>> Tuple unpacking already works in foreach.  This code has compiled since
>> at least 2.063.2:
>>
>> import std.stdio;
>> import std.range;
>> void main(string[] args)
>> {
>>     auto tuples = ["a", "b", "c"].zip(iota(0, 3));
>>
>>     // unpack the string into `s`, the integer into `i`
>>     foreach (s, i; tuples)
>>         writeln(s, ", ", i);
>> }
>
> Does this work for more than 2 values?  Can the first value be something other than an integer?

Answered this myself.  What is supported is:

	foreach(key, value; tuple) { }

But, what is not supported is more than 2 values.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
February 21, 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:
>
>> 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/
February 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:
>
>> On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan@netmail.co.nz> wrote:
>>
>>> Only if the compiler prefers opApply to range methods, does it?
>>
>> It should. If it doesn't, that is a bug.
>>
>> The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply.
>
> Thanks.
>
> So, if we had this support which I am asking for:
>
> 	foreach(index, value; range) { }
>
> And, if someone adds opApply to that range, with a different type for the first variable then an existing foreach (using index, value) is likely to stop compiling due to type problems.
>
> This seems acceptable to me.

I think any type that does both opApply and range iteration is asking for problems :) D has a nasty way of choosing "all or nothing" for overloads, meaning it may decide "this is a range" or "this is opApply", but if you have both, it picks one or the other.

I'd rather see it do:

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

This may be how it works, I honestly don't know.

> There is an outside chance it might keep on compiling, like if 'i' is not used in a strongly typed way, i.e. passed to a writefln or similar.  In this case we have silently changed behaviour.
>
> Is this acceptable?

Adding opApply is changing the API of the range. If the range does something different based on whether you use the range interface or opApply, then this is a logic error IMO.

The easiest thing is to just not use opApply and range primitives together :) One separation I like to use in my code is that you use opApply on a container, but range primitives on a range for that container. And a container is not a range.

-Steve
February 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:
>
>> On Thu, 20 Feb 2014 17:09:31 -0000, Steven Schveighoffer <schveiguy@yahoo.com> wrote:
>>
>>> On Thu, 20 Feb 2014 11:07:32 -0500, Regan Heath <regan@netmail.co.nz> wrote:
>>>
>>>> Only if the compiler prefers opApply to range methods, does it?
>>>
>>> It should. If it doesn't, that is a bug.
>>>
>>> The sole purpose of opApply is to interact with foreach. If it is masked out, then there is no point for having opApply.
>>
>> Thanks.
>>
>> So, if we had this support which I am asking for:
>>
>> 	foreach(index, value; range) { }
>>
>> And, if someone adds opApply to that range, with a different type for the first variable then an existing foreach (using index, value) is likely to stop compiling due to type problems.
>>
>> This seems acceptable to me.
>
> I think any type that does both opApply and range iteration is asking for problems :) D has a nasty way of choosing "all or nothing" for overloads, meaning it may decide "this is a range" or "this is opApply", but if you have both, it picks one or the other.
>
> I'd rather see it do:
>
> 1. can I satisfy this foreach using opApply? If yes, do it.
> 2. If not, can I satisfy this foreach using range iteration?
>
> This may be how it works, I honestly don't know.
>
>> There is an outside chance it might keep on compiling, like if 'i' is not used in a strongly typed way, i.e. passed to a writefln or similar.  In this case we have silently changed behaviour.
>>
>> Is this acceptable?
>
> Adding opApply is changing the API of the range. If the range does something different based on whether you use the range interface or opApply, then this is a logic error IMO.
>
> The easiest thing is to just not use opApply and range primitives together :) One separation I like to use in my code is that you use opApply on a container, but range primitives on a range for that container. And a container is not a range.

Makes sense to me. :)

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
February 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. 

February 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...
>
>> 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