scalamutabilitypass-by-name

Why does this Iterator infinitely loop?


I'm attempting to chain Iterators:

var it = Iterator(1)
it.next
it = Iterator(2) ++ it
it.next
it.hasNext

This infinitely loops on the hasNext as you can see here: https://scastie.scala-lang.org/qbHIVfsFSNO5OYmT4pkutA

If you run this and inspect the stack while it's infinitely looping, it's looping in the concetentation:

        at scala.collection.Iterator$ConcatIterator.merge(Iterator.scala:213)
        at scala.collection.Iterator$ConcatIterator.advance(Iterator.scala:197)
        at scala.collection.Iterator$ConcatIterator.hasNext(Iterator.scala:227)

(This stack is from Scala 2.12.11, but the Scastie link shows same behavior in 2.13.2).

I know that one should never use an iterator after calling a method on it, but this appears like it would work to me. Using the var to point to the "current" Iterator and changing it to point to a new Iterator that appends the remainder of the previous one.

The following slight modification does work:

var it = Iterator(1)
it.next
val x = it
it = Iterator(2) ++ x
it.next
it.hasNext

Scastie link: https://scastie.scala-lang.org/1X0jslb8T3WIFLHamspYAg

This suggests to me that somehow the broken version is creating an Iterator that is appending itself. Any hints as to what is going on here?


Solution

  • The argument to the ++ method of Iterator is passed by name. ++ returns a new Iterator that just stores a function which returns it, but doesn't call it until you try to use the appended elements.

    So ++ tries to evaluate the argument only when you call it.hasNext, but by that time it is already redefined as the result of ++, so it ends up trying to append it to itself.

    In other words vars and by-name parameters don't work together.

    So don't reassign Iterator method results to the same variable and give them new names instead:

    val it = Iterator(1)
    it.next
    val it2 = Iterator(2) ++ it
    it2.next
    it2.hasNext