Why can't the Iterator work correctly?

That seems to me like the expected behavior for duplicate. I don’t see any other way it could behave.

After calling duplicate on it you should discard it and only work with iterators that duplicate gives you.

1 Like

After using the duplicate method, shouldn’t it be exhausted automatically? In other words, if I accidentally use the old one, there will be a serious mistake, is it? But the book says “By contrast the original iterator, it, is advanced to its end by duplicate and is thus rendered unusable”. I expected the interpreter throws an exception if I use the old one rather than I should remember that the old one can’t be used. When the code is long, programmer may make a careless mistake.

Why? I expected the interpreter throws an exception that prohibits me to use the old iterator.

You can certainly imagine a design where invalidated iterators are forcibly invalidated by either rendering them empty (but without actually consuming any items), or by making them throw a runtime error if any of their methods are called.

Such a design hadn’t occurred to me. I’m not sure if such a design was ever considered.

Expecting anything after you used a method on an Iterator that’s not isEmpty, next or hasNext is wrong – it’s undefined behaviour.

This coincidentally is the third thread about iterator re-use I’ve seen in a short period of time. What’s the underlying cause of this question?

Not sure about the timing, but Iterators can be confusing to Scalani, because the API looks so much like that of an immutable standard Scala collection (i.e. things we use every day), but it does not behave like one.

Ironically, you do not need Iterators. Something like a lazy linked list could do the job at least as well.

Isn’t the problem with a lazy list or stream always that it’s way too easy to hold on to the head by accident, preventing all subsequent elements from being garbage collected?
Isn’t a View (in 2.13 at least…) supposed to be more or less an Iterator that can be reused?

Not always, lazy list still memorizes, and prevents collection if you hold on to its head. Iterators don’t do that.

Right, you’d have to always discard the head and keep the tail to replace an iterator.

Absolutely right. You would typically want to obtain the lazily linked list and immediately consume and discard it - just like you would with an iterator.

If you start holding onto a lazily linked list and pass it around like candy, then the benefit of the laziness gets lost. On the other hand, do the same with an iterator and likely you will find it in a state different from what you expected.

AKAIK, views keep the entire collection in memory, so they are no substitute.

If the Scala community prefers iterators to lazily linked lists, that would be the first time I am more FP-minded than the community. :wink:

I suppose you could do something similar to Java’s fail-fast iterators: use a flag to detect that a method has been called and check for it everywhere to throw an IllegalStateException explicitly. Small runtime cost but would avoid incorrect code that appears to be working fine until an internal implementation changes.

3 Likes

This might be harder than it sounds.

How would you, for example, implement duplicate?

I don’t think it’s that hard, actually… Just a bit tedious. As far as I can see duplicate would stay almost completely the same.
I guess you’d have to do more or less this, for every method that consumes the current Iterator:

private[this] var invalidated = false
final protected def checkInvalidated() = {
  if (invalidated) throw new IllegalStateException
  else invalidated = true
}

def duplicate = {
  checkInvalidated()
  ...
}

def map[B](f: A => B) = {
  checkInvalidated()
  ...
}

...

And then some slight variation for methods that don’t consume the Iterator.
Only protecting next and hasNext seems hard to impossible, but that’s low-level API anyway.

We already have a means of duplicating iterators with no need for state checking: Iterable. Calling iterator simply returns a new iterator. Don’t know if that helps.

I’m not entirely sure what you’re proposing here: are you proposing that you would not throw an IllegalStateException on next and hasNext – the core abstraction of Iterator?

Iterator has no iterator method – and if it had, it would be useless, since it would invalidate the source iterator, and could just as well return identity.

Iterable, see:

https://www.scala-lang.org/api/2.12.3/scala/collection/Iterable.html

The discussion isn’t about Iterable, it’s about Iterator

More or less, yes :sweat_smile:
It appears to be close to impossible to do it, and I wouldn’t recommend user code calling next or hasNext anyway. It’s a solution that looks terrible, is still not very easy, and not very maintainable either… But would catch most bugs.