-
Notifications
You must be signed in to change notification settings - Fork 70
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
\iter\isEmpty()
can indirectly cause unexpected consumption from iterators in rare cases
#97
Comments
I don't think it's possible to do anything against this, apart from rejecting I'd consider a side-effecting IteratorAgggregate implementation to be a bug -- where did you encounter this? |
The side-effecting class MyClass implements \IteratorAggregate {
public function getIterator(): \Traversable {
// some logic which mutates global mutable state
while ($item = array_pop($someGlobalState)) { yield $item; }
}
} So, to be clear, the buggy It just so happens that there is a bit of a footgun here that I've encountered; I've shot myself in the foot with a valid (if not necessarily best practice) implementation of I think I agree with you here that there's not much code-wise we can do here while still handling |
Interesting case! The actual IteratorAggregate implementation here is side-effect free -- getIterator() will just return the Generator. The problem is that the call to valid() has a side-effect. For generators, the only way to determine whether there are any more elements is to actually complete the next one, and that has a side-effect in your case. When working on an Iterator, this is fine, because even though valid() has a side-effect, it will not be executed a second time when calling current(). The computed value is stored. But for IteratorAggregate, we will execute the side effect twice. I can't really blame you for this one -- I think your code looks perfectly reasonable, and there's really no other way to implement it short of a manual Iterator implementation, which could implement a side-effect-free valid() method.
So yeah, I think a documentation note is probably the best we can do here. |
I close my PR then if, it fixes the problem but it's not in really good way |
I've come across a rare case where the behavior of
\iter\isEmpty()
can indirectly cause unexpected consumption from iterators in normal use cases.Specifically, in the case where
isEmpty
is provided an object of\IteratorAggregate
, the object'sgetIterator()
method is called. Any further uses of this object may callgetIterator()
a second time, so if this method is not a pure function then the first item(s?) we might expect from the iterator could be lost to the void.I've put together what I believe is a minimal reproduction case here:
https://gist.github.com/athrawes/e451484fdb4d0646f9aa03c9e47b566a
Basically, if the item passed to
isEmpty
looks like this:then the following does not behave as expected:
Then interacting with that item in the future with any
foreach
constructs or any methods in this library will be missing some item(s?) from the front of the iterable.I'm not sure if this is really a bug in this library per se, but it is rather unexpected - especially as the docblock on
isEmpty
claims to not consume items from iterators. Would this simply be a matter of warning users about this edge case, or is there some way to handle this here?The text was updated successfully, but these errors were encountered: