在ECMAScript 6 规范草案中StopIteration
,使用异常来表示迭代结束而不是使用专用方法(hasNext
在 Java/Scala 和MoveNext
C# 中)来检查迭代结束的基本原理是什么。
抛开潜在的性能问题不谈,在我看来,异常不应该用于并非真正异常的事情。
在ECMAScript 6 规范草案中StopIteration
,使用异常来表示迭代结束而不是使用专用方法(hasNext
在 Java/Scala 和MoveNext
C# 中)来检查迭代结束的基本原理是什么。
抛开潜在的性能问题不谈,在我看来,异常不应该用于并非真正异常的事情。
我不声称这个答案是权威的。相反,这只是我对我读到的关于迭代器和/或我自己的想法的各种讨论的回忆......不幸的是,我没有一个来源列表(很久以前),我也不能轻易产生一个(因为即使是谷歌搜索也需要有时很多时间)。
StopIteration
名称StopIteration
和许多语义都源自python。
PEP 234对替代方法有一些评论:
有人质疑表示迭代结束的异常是否不太昂贵。已经提出了 StopIteration 异常的几种替代方案:一个特殊的值 End 来表示结束,一个函数 end() 来测试迭代器是否完成,甚至重用 IndexError 异常。
特殊值的问题是,如果一个序列曾经包含该特殊值,则对该序列的循环将在没有任何警告的情况下提前结束。如果使用空终止的 C 字符串的经验没有告诉我们这可能导致的问题,想象一下 Python 自省工具会在遍历所有内置名称列表时遇到的麻烦,假设特殊的 End 值是内置的 -名义上!
调用 end() 函数每次迭代需要两次调用。两次调用比一次调用加上一次异常测试要昂贵得多。尤其是时间紧迫的 for 循环可以非常便宜地测试异常。
重用 IndexError 可能会导致混淆,因为它可能是一个真正的错误,过早结束循环会掩盖它。
hasNext()
JavahasNext()
只是一个“奖金”。next()
然而,Java有其自己的StopIteration
被调用风格NoSuchElementException
。
hasNext()
即使不是不可能,也很难实现,最好用下面的生成器来演示:
var i = 0;
function gen() {
yield doSomethingDestructive();
if (!i) {
yield doSomethingElse();
}
}
您将如何实施hasNext()
?您不能简单地“窥视”生成器,因为这实际上会执行doSomethingDestructive()
,这是您不打算执行的,否则您会next()
首先调用。
因此,您必须编写一些代码分析器,该分析器必须始终可靠地证明给定代码在以某种状态运行时将始终导致良率或无良率(停止问题)。因此,即使您可以编写类似的内容,状态仍可能.hasNext()
在.next()
.
hasNext()
在以下示例中将返回什么:
var g = gen();
g.next();
if (g.hasNext()) {
++i;
alert(g.next());
}
那个时候true
才是正确的答案。但是,后续.next()
会抛出,用户可能很难弄清楚为什么......
你可以告诉人们:“不要在你的迭代器实现中做以下事情”(合同)。人们会经常打破和抱怨。
因此,以我的拙见,hasNext()
这是构思不周的,并且很容易编写错误的代码。
.MoveNext()
C#
与 几乎相同.next()
,只是返回值指示是否实际上存在下一项而不是或缺少异常。
但是,有一个重要的区别:您需要将当前项存储在迭代器本身中,例如.Current
在 C# 中,这可能会不必要地延长当前项的生命周期:
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
好的,这个例子有点做作,但我确信在实际用例中,生成器/迭代器会返回一些占用大量空间或占用资源的东西。
在该StopIteration
方案中,buf
一旦超出范围就可以进行垃圾收集。在中,直到再次调用.MoveNext()
它才能被垃圾收集,因为它仍然会持有对它的引用。.next()
.Current
结论
在我看来,在所提出的替代方案中,StopIteration
一种是最不容易出错和最不模棱两可的方法。如果我没记错的话,python 和 es-6 的人也是这么想的。