Hi everyone, I just discovered a weird discrepancy for
Iterator between scala 2.12 and 2.13. And wondering to know if this is a bug of 2.12, or a behavior change in 2.13?
In scala 2.12.11 REPL:
scala> val it = Iterator(1,2,3)
it: Iterator[Int] = <iterator>
res0: List[Int] = List(1)
res1: List[Int] = List(1)
res2: List[Int] = List(1)
In scala 2.13.3 REPL:
scala> val it = Iterator(1,2,3)
val it: Iterator[Int] = <iterator>
val res14: List[Int] = List(1)
val res15: List[Int] = List(2)
val res16: List[Int] = List(3)
As you can see, in 2.12, the underlying state is not shared between source and derivative iterators. So
take() does not move current element forward, while in 2.13 the opposite is true.
So is this expected in 2.12 that derived iterator not sharing state with parent iterator, or is this a bug?
From the docs (https://www.scala-lang.org/api/2.12.11/scala/collection/Iterator.html):
An iterator is mutable: most operations on it change its state.
You seem to assume the opposite, which is just not true in general.
edit: I see no bug here, it states that it is mutable and that means it can change, or not, but that is an implementation detail.
Also, note the clear warning for the take method (https://www.scala-lang.org/api/2.12.11/scala/collection/Iterator.html#take(n:Int):Iterator[A]):
Reuse: After calling this method, one should discard the iterator it was called on, and use only the iterator that was returned. Using the old iterator is undefined, subject to change, and may result in changes to the new iterator as well.
Hi, thanks for replying. I do assume
.take() or other method will change its state. So I thought 2.13’s behavior was what I expected. i.e., calling
.take() will change its current element.
I get your point that mutation is not guaranteed. If so, I have another question:
How to easily iterate an iterator one batch of elements at a time? For example, I wanna take the first 10 elements from iterator, then the next 10 elements, until it’s exhausted. I thought
.take() would do that. It seems it’s not guaranteed.
Sorry I didn’t see this, because I was referencing the latest doc (2.13), which doesn’t have this warning.
You probably want to use
Thank you! Exactly what I needed.
The 2.13 docs say this:
It is of particular importance to note that, unless stated otherwise, one should never use an iterator after calling a method on it . The two most important exceptions are also the sole abstract methods:
Behavior when reusing such an
Iterator is undefined.
So basically when working with iterators you can either use the high-level API in a pipeline fashion without reusing intermediary iterators (e.g.
it.map(...).take(...).filter(...).drop(...).toList). Or you can implement some custom low-level logic with
sliding is perfect for taking a series of chunks. To take just one chunk, use
scala 2.13.4> val i = Iterator.range(1, 10)
val i: Iterator[Int] = <iterator>
scala 2.13.4> val (some, more) = res0.splitAt(3)
val some: Iterator[Int] = <iterator>
val more: Iterator[Int] = <iterator>
scala 2.13.4> some.toList
val res2: List[Int] = List(1, 2, 3)
scala 2.13.4> more.toList
val res3: List[Int] = List(4, 5, 6, 7, 8, 9)
sliding, given the question (“the first 10 elements from iterator, then the next 10 elements, …”).
But be careful with using
splitAt in a loop or a recursive function. Last time I tried, this created long chains of iterators that blew in my face on large iterations.