In Draft ECMAScript 6, what's the rationale behind using StopIteration exception to signal the end of iteration?

StackOverflow https://stackoverflow.com/questions/18304110

質問

In Draft ECMAScript 6 Specification, what's the rationale behind using StopIteration exception to signal the end of iteration instead of using a dedicated method (hasNext in Java/Scala, and MoveNext in C#) to check for the end of iteration.

Potential performance problem aside, in my opinion, exception should not be used for something that's not really an exception.

役に立ちましたか?

解決

I don't claim to be authoritative with this answer. Instead, it is just my recollection of various discussions I read about iterators and/or my own thoughts... Unfortunately I do not have a list of sources (too long ago) nor can I easily produce one (because even googling up stuff takes lots of time sometimes).

StopIteration

The name StopIteration and a lot of the semantics originated from python.

PEP 234 has some remarks regarding alternative approaches:

  • It has been questioned whether an exception to signal the end of the iteration isn't too expensive. Several alternatives for the StopIteration exception have been proposed: a special value End to signal the end, a function end() to test whether the iterator is finished, even reusing the IndexError exception.

    • A special value has the problem that if a sequence ever contains that special value, a loop over that sequence will end prematurely without any warning. If the experience with null-terminated C strings hasn't taught us the problems this can cause, imagine the trouble a Python introspection tool would have iterating over a list of all built-in names, assuming that the special End value was a built-in name!

    • Calling an end() function would require two calls per iteration. Two calls is much more expensive than one call plus a test for an exception. Especially the time-critical for loop can test very cheaply for an exception.

    • Reusing IndexError can cause confusion because it can be a genuine error, which would be masked by ending the loop prematurely.

hasNext()

Java's hasNext() is just a "bonus". Java's next(), however, has it's own flavor of StopIteration called NoSuchElementException.

hasNext() is hard, if not impossible, to implement, which is best demonstrated with e.g. the following generator:

var i = 0;
function gen() {
  yield doSomethingDestructive();
  if (!i) {
    yield doSomethingElse();
  }
}

How would you implement hasNext() for that? You cannot simply "peek" into the generator, because that would actually execute doSomethingDestructive(), which you didn't intend to do or else you would have called next() in the first place.

So you'd have to write some code analyzer that has to always reliably proof that a given code when run with a certain state will always result in either a yield or no yield (halting problem). So even if you could write something like that, the state may still change between .hasNext() and a subsequent .next().

What would hasNext() return in the following example:

var g = gen();
g.next();
if (g.hasNext()) {
  ++i;
  alert(g.next());
}

At that point of time true would be the right answer. However, the subsequent .next() would throw, and the user would probably have a hard time figuring out why...

You could tell people: "Don't do the following things in your iterator implementation" (a contract). Which people will routinely break and complain.

So in my humble opinion, hasNext() was ill-conceived and makes it too easy to write wrong code.

.MoveNext()

C# is pretty much the same as .next(), just that the return value indicates if there was actually a next item instead of or the lack of an exception.

There is one important difference, however: You need to store the current item in the iterator itself, e.g. .Current in C#, which may prolong the life-time of the current item unnecessarily:

var bufferCtor = (function gen() {
  for(;;) {
    yield new ArrayBuffer(1<<20); // buffer of size 1 megabyte
  }
})();

setInterval(function() {
  // MoveNext scheme
  bufferCtor.MoveNext();  // Not actually Javascript
  var buf = bufferCtor.Current; // Not actually Javascript
  // vs. StopIteration scheme
  var buf = bufferCtor.next();
}, 60000); // Each minute 

OK, that example is a little contrived, but I'm sure there are actual use cases where a generator/iterator would return something talking up a lot of space or otherwise occupying resources.

In the StopIteration scheme, buf can be garbage-collected as soon as it goes out of scope. In .MoveNext(), it cannot be garbage-collected until .next() is called again, because .Current will still hold a reference to it.

Conclusion

In my opinion, of the presented alternatives, the StopIteration one is the least error prone and least ambiguous approach. And if I recall correctly, the python and es-6 people thought about the same.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top