在草案ECMAScript 6中;s使用StopIteration异常来表示迭代结束的基本原理

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

本文关键字:结束 迭代 表示 异常 ECMAScript 草案 StopIteration 使用      更新时间:2023-09-26

在ECMAScript 6规范草案中,使用StopIteration异常来表示迭代结束,而不是使用专用方法(Java/Scala中的hasNext和C#中的MoveNext)来检查迭代结束,其原理是什么。

撇开潜在的性能问题不谈,在我看来,异常不应该被用于一些实际上不是异常的东西。

我并不声称这个答案具有权威性。相反,这只是我对我读到的关于迭代器的各种讨论和/或我自己的想法的回忆。。。不幸的是,我没有一个来源列表(很久以前),也无法轻松生成一个(因为有时即使在谷歌上搜索也需要很多时间)。

StopIteration

名称StopIteration和许多语义都源自python。

PEP234有一些关于替代方法的评论:

  • 有人质疑是否有一个例外标志着迭代并不太昂贵。StopIteration异常已被提出:一个特殊值End为了发出结束信号,函数end()测试迭代器是否已完成,甚至重复使用IndexError异常。

    • 一个特殊值的问题是,如果一个序列包含该特殊值,则该序列上的循环将在没有任何警告的情况下过早结束。如果与以null结尾的C字符串并没有教会我们这个问题可以引起,想象一下Python内省工具的麻烦将对所有内置名称的列表进行迭代,假设特殊的End值是一个内置名称!

    • 调用end()函数需要每个调用两次迭代。两个电话比一个电话贵得多加上一个异常测试。尤其是在时间紧迫的时候for循环可以非常廉价地测试异常。

    • 重用IndexError可能会导致混乱,因为它可能是真正的错误,将通过结束循环来掩盖过早地。

hasNext()

Java的hasNext()只是一个"奖励"。然而,Java的next()有自己的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());
}

在那个时候CCD_ 18将是正确的答案。然而,随后的.next()会抛出,用户可能很难弄清楚为什么。。。

你可以告诉人们:"不要在迭代器实现中做以下事情"(契约)。人们会经常打断并抱怨。

因此,在我看来,hasNext()考虑不周,很容易编写错误的代码。

.MoveNext()

C#.next()几乎相同,只是返回值指示是否真的有下一个项目,而不是异常。

然而,有一个重要的区别:您需要将当前项存储在迭代器本身中,例如C#中的.Current,这可能会不必要地延长当前项的寿命:

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方案是最不容易出错和最不含糊的方法。如果我没有记错的话,蟒蛇和es-6人也有同样的想法。