Getting the Correct Iterator

209 Views Asked by At

The intention behind the first snippet of code below is to define a new trait for iterables that provides an additional method for prepending a new element in front of the iterable.

However, when running the code in the second snippet, we see that the +: method returns an iterable yielding an infinity of 0s.

What am I doing wrong and how can I get the intended behavior?

Note: I added the outer val to make sure I get the correct iterator when defining the methods of the object returned by +:; I don't know how to get access to that iterator otherwise (Iterable2.this.iterator did not compile).

trait Iterable2[A] extends Iterable[A] {
  val outer :Iterable[A] = this
  def +:(elem :A) = new Iterable2[A] {
    override def iterator: Iterator[A] = new Iterator[A] {
      private[this] var virgin = true
      override def hasNext: Boolean = virgin || outer.iterator.hasNext
      override def next(): A = {
        if (virgin) {virgin = false; elem}
        else outer.iterator.next()
      }
    }
  }
}
val i = new Iterable2[Int] {
  override def iterator: Iterator[Int] = Iterator(1,2,3)
}

for (j <- 0 +: i) {
  println(j)
}
2

There are 2 best solutions below

0
On BEST ANSWER

The first error in my original code is that outer.iterator returns a new iterator each time it's evaluated, as pointed out by @DiegoMartinoia and @Imm.

However, it is also necessary to replace val outer = this with outer =>, as suggested by @misberner.

For now, this is only a partial answer to my question, as it doesn't explain why the second change is necessary.

The corrected trait code is thus:

trait Iterable2[A] extends Iterable[A] {
  outer :Iterable[A] =>
  def +:(elem :A) = new Iterable2[A] {
    override def iterator: Iterator[A] = new Iterator[A] {
      private[this] var virgin = true
      private[this] val underlyingIterator = outer.iterator
      override def hasNext: Boolean = virgin || underlyingIterator.hasNext
      override def next(): A = {
        if (virgin) {virgin = false; elem}
        else underlyingIterator.next()
      }
    }
  }
}
1
On

outer.iterator will always give you a new iterator. You need to create one and stash it somewhere, then use that single stashed one rather than creating a new one every time:

new Iterator[A] {
  val outerIterator = outer.iterator
  override def hasNext = ...
}