Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Issue 15860 - lockstep should support foreach_reverse #4138

Merged
merged 1 commit into from
Apr 26, 2016

Conversation

Biotronic
Copy link
Contributor

This fixes https://issues.dlang.org/show_bug.cgi?id=15860 by adding opApplyReverse to Lockstep.

@dlang-bot
Copy link
Contributor

Fix Bugzilla Description
15860 lockstep should support foreach_reverse

auto arr1 = [ 0, 1, 2, 3 ];
auto arr2 = ['a', 'b', 'c', 'd'];

foreach_reverse (index, a, b; lockstep(arr1, arr2))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't actually prove the iteration is going in reverse.

@Biotronic Biotronic force-pushed the fix-15860 branch 3 times, most recently from 0480268 to 180f8ed Compare April 6, 2016 04:23
@DmitryOlshansky
Copy link
Member

LGTM

{
indexDef = q{
size_t index = ranges[0].length-1;
enforce(_stoppingPolicy == StoppingPolicy.requireSameLength, "lockstep can only be used with foreach_reverse when stoppingPolicy == requireSameLength");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we support the shortest? It's just min

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just because it'd be wrong. Consider:
auto a = [1,2];
auto b = [3];
foreach_reverse (i, e1, e2; lockstep(a, b)) {
assert(e1 == a[i]); // passes
assert(E2 == b[i]); // fails
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go with shortest, II expect I to be zero in the first and only iteration. Or am I missing something?

For the bidirectional ranges you have to call popBack a couple of times, with random access it's even easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, I misunderstood. So instead we just skip a few elements. I guess that works (it does mirror the behavior when iterating forward). The problem I see is that's an unknown cost. When iterating forward, skipping the remaining items is free. If requireSameLength had been the default, I'd be more positive, but currently that'd be a hidden cost of unknown size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but currently that'd be a hidden cost of unknown size.

yep but only for bidirectional ranges + foreach_reverse.
I would exactly expect this behavior - it's a lot better than throwing an error imho.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That leaves off the 1 from the first array. If that's not an array, but e.g. some bidirectional generator, I'd still expect it to iterate from the end forward. Since backward iteration without an index doesn't require hasLength, it's not possible to have the behavior you describe without the index. So either the behavior is different when using an index, or we simply disallow the cases where the behavior would differ

Also would prefer current behaviour.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping @Biotronic - do you mind to update your pr to support shortest according to the current behavior?

Copy link
Contributor Author

@Biotronic Biotronic Apr 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm... we just agreed on the polar opposite of that. Supporting indexed reverse iteration with shortest will either:

  • break symmetry with regular foreach_reverse (indexes would be wrong). Or
  • incur a hidden cost to skip trailing elements.

I am completely unwilling to violate the first of these, and very reluctant to break the latter.

For non-indexed foreach_reverse, shortest is already supported and behaves the way you want:

foreach_reverse (e1, e2; lockstep([1,2,3], [4,5])) {
    // Will iterate over (3,5) and (2,4) in that order.
}

Copy link
Member

@wilzbach wilzbach Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the current behavior:

foreach_reverse (e1, e2; lockstep([1,2,3], [4,5])) {
assert(e1 == e2 - 2);
}

I guess that's why I got confused.
@DmitryOlshansky did you mean with "Also would prefer current behaviour." disallowing "shortest"?
I think I will just stop annoying you - I am wasting your time on such a rare edge case that can also be solved easier e.g. with retro - s.t. it took a longer time for its lack even to be discovered.

break symmetry with regular foreach_reverse (indexes would be wrong)

foreach_reverse (i, e1, e2; lockstep([1,2,3], [4,5])) {
  // (2,3,5), (1, 4, 2)
}

For non-indexed foreach_reverse, shortest is already supported and behaves the way you want:

Test please ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test please ;-)

A test has been added.

break symmetry with regular foreach_reverse (indexes would be wrong)

foreach_reverse (i, e1, e2; lockstep([1,2,3], [4,5])) {
  // (2,3,5), (1, 4, 2)
}

Yup, that breaks indexing - 5 is not element 2 in the array [4,5]. Writing it slightly differently, both asserts below should hold:

auto a = [1,2,3];
auto b = [4,5];
foreach_reverse(i, e1, e2; lockstep(a,b)) {
    assert(e1 == a[i]);
    assert(e2 == b[i]);
}

I think I will just stop annoying you

Please, you're not annoying - I am very glad you sparked the discussion, as it made me consider alternative solutions. All solutions have trade-offs, and I feel the ones I've chosen best fit the D philosophy, but that is certainly debatable.

I've given some of the reasons I've chosen what I have, above. Another reason is that anyone can easily add indexing with iota or an explicit counter variable - this way it's an opt-in cost, and you have to consider the trade-offs yourself.

Lastly, there is no "intuitive" way for it to behave, so the default behavior would confuse some portion of users no matter what. Throwing an exception is not optimal, but at least it gives an understandable error message instead of iterating over a different set of values than the user intended.

@Biotronic
Copy link
Contributor Author

Finally added some documentation of the new behavior.

Iterating over $(D Lockstep) in reverse and with an index is only possible
when $(D s) == $(D StoppingPolicy.requireSameLength), in order to preserve
indexes. If an attempt is made at iterating in reverse when $(D s) ==
$(D StoppingPolicy.shortest), and exception will be thrown.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"an" exception ;-)

@Biotronic Biotronic force-pushed the fix-15860 branch 6 times, most recently from cc8f35a to 87bb8e3 Compare April 19, 2016 18:56
assert(a == 3 && b == 5);
if (n == 1)
assert(a == 2 && b == 4);
n++;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you just compare with n as index like in your examples? Looks a bit weird this way

Copy link
Contributor Author

@Biotronic Biotronic Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Sorry for the wait. :p

@wilzbach
Copy link
Member

I feel the ones I've chosen best fit the D philosophy, but that is certainly debatable.

I think I agree now ;-)
Let's go with it!

@DmitryOlshansky
Copy link
Member

Sounds good let's roll

@DmitryOlshansky
Copy link
Member

Auto-merge toggled on

@DmitryOlshansky DmitryOlshansky merged commit 23fcb24 into dlang:master Apr 26, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants