-
Notifications
You must be signed in to change notification settings - Fork 21
Iterator.toIndexedSeq calls hasNext() multiple times in Scala 2.13.0-RC1 (preview) #11455
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
Comments
It should probably be said though that the contract of |
@Sciss There has been no guarantee nor any specification that Iterator.hasNext() must return the same result for subsequent calls before calling next() https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html#hasNext-- |
It also means hasNext() is checked multiple times in toIndexedSeq, so some redundancy is introduced in the recent change. |
@xerial I don't disagree that calls might be redundant, but I think you are mistaken to assume There are many discussions on the Internet, e.g. https://coderanch.com/t/378826/java/Iterator-hasNext-idempotent I insist your code is buggy if |
Actually fixing my code is not a big deal, but I don't know how many Iterator implementations are assuming that hasNext() will be called only once for each move. |
I'm not arguing Iterator.haxNext() must be called only once. Rather I mean releasing Scala 2.13.0-RC1 with this behavior change will reveal how many Iterator.hasNext() implementations are non-idempotent, and we need to ask all dependent Scala/Java library maintainers to fix such Iterator implementations. I don't think this is a good timing to do so, because Scala 2.12 to 2.13 is already a big jump and has been causing many pains for library maintainers. |
The first hasNext call happens in VectorBuilder:
And IterableOnce.copyToArray will call hasNext again:
|
@joshlemer Do we have any workaround to avoid calling Iterator.hasNext() multiple times in Vector? This optimization is introduced at scala/scala#7588 |
I don't consider this a bug. Iterators that can't consistently present what elements they have aren't necessarily going to end up with a maximal representation as a sequence. Relying on any particular series of calls to In this case there's a simple recursive function or do-while loop that avoids the extra call (at the cost of a bit of extra work if the iterator starts out empty), but I don't think we should promise anything. Relying on fragile undocumented behavior for correctness is almost never a good way to go. |
@Ichoran Yes. It's not a bug, rather it's a behavior change that was introduced just 7 days ago scala/scala#7588 that will affect all poorly implemented Iterators. And finding the cause of such behavior change will be tricky because only the first element will be missing without any error. Some DBMS implementation that might be relying on the previous behavior will cause incorrect results. This kind of bug will be hard to find. |
Workaround
Wrap your unruly iterators in that. |
Another workaround: Replace myUnrulyIterator.toIndexedSeq with
|
@Ichoran My point is how many such fixes we need to make against the existing Scala/Java libraries because of this behavior change. It's not only about my code. I don't have any numbers about how many libraries will start to produce missing results silently. |
@xerial actually the regression/change is a result of https://github.com/scala/scala/pull/7110/files#diff-59f3462485b74027de4fd5e9febcc81bR744, not the PR merged a few days ago. I don't have a very strong opinion whether this should be considered a bug or not, but if it will be considered a bug I would be willing to help think/write an alternative implementation which calls |
Okay, but this was just It's an enormous minefield. It'd be good if we could help people catch their sketchy iterators, but I don't think we can maintain that this is a behavior change that leads from safe expected behavior into unexplained danger. The danger was always there and huge; some people lucked out in some scenarios. |
Josh -- FWIW, you can attempt the copy even if it's empty, and terminate if it says it copied zero elements. (Use a tail-recursive method or do-while.) |
@Ichoran
|
I don't think this will work quite so simply, since the val it = (xs.iterator : Iterator[A]).asInstanceOf[Iterator[AnyRef]]
while (it.hasNext) {
advanceToNextBlockIfNecessary()
display0(lo) = it.next()
lo += 1
lo += it.copyToArray(xs = display0, start = lo, len = display0.length - lo)
}
this
|
I would be surprised if there are many There is nothing in the docs that implies that
If there are obvious places where |
@szeiger I understand there is no practical way to guarantee to call How about thinking this as an optimization problem? Actually, the initial behavior change was made for
So this optimization is making a trade-off between:
For B), we can amortize the memory allocation cost if copying empty Iterator or iterator that has exactly the same number of elements with (block size) x M (= at the boundary of Vector blocks) is quite rare. And also if Iterator implementation prepares a cache internally, we need to compare two things:
A1) is memory efficient, but A2) can be much faster because the cache for iterating the elements will be hot. |
I know I have, in previous years, submitted a fix that eliminated an extra call to hasNext. (Is there an emoji for pats self on back?) Closing this ticket because no such outcome resulted in this instance. A PR for optimizations is always welcome. (Just guessing.) But since no contract is violated, in this instance, there is no outcome to track with a ticket. This was an interesting thread, but I stopped reading when I encountered bullet points. (I'm just scanning for something to do this weekend. I've been laid up in bed a couple of days. This ticket doesn't rise to that level of urgency.) |
Problem:
next()
https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html#hasNext--Code Example
This code is in https://github.com/xerial/scala-bug-repro
How to reproduce:
For the other Scala versions
This behavior is found at #11453
Cause
VectorBuilder.addAll calls the Iterator.hasNext to check whether it should advance to the next block or not:
Related change: scala/scala#7588
The text was updated successfully, but these errors were encountered: