在ECMAScript 6草案中,使用StopIteration异常来表示迭代结束的原因是什么?

时间:2013-08-18 22:05:59

标签: javascript iterator ecmascript-harmony ecmascript-6

Draft ECMAScript 6 Specification中,使用StopIteration异常来表示迭代结束而不是使用专用方法(Java / Scala中的hasNextMoveNext)的原因是什么?在C#中检查迭代的结束。

除了潜在的性能问题之外,在我看来,异常不应该用于那些并非真正例外的事情。

1 个答案:

答案 0 :(得分:2)

我并不认为这个答案具有权威性。相反,这只是我对各种讨论的回忆,我读到了关于迭代器和/或我自己的想法...不幸的是我没有一个源列表(很久以前)也不能轻易地生成一个(因为甚至谷歌搜索的东西需要有时很多时间。)

<强> StopIteration

名称StopIteration和许多语义来自python

PEP 234对替代方法有一些评论:

  
      
  • 有人质疑是否有异常信号结束     迭代不是太贵。几个替代品     已提出StopIteration异常:特殊值End     发信号结束,一个函数end()来测试是否是迭代器     已完成,甚至重用IndexError异常。

         
        
    • 一个特殊的值有一个问题,如果一个序列   包含该特殊值,该序列的循环将   过早地结束,没有任何警告。如果有经验的话   以null结尾的C字符串并没有教会我们这个问题   可以导致,想象一下Python内省工具的麻烦   会迭代所有内置名称的列表,   假设特殊的End值是内置名称!

    •   
    • 调用end()函数需要每次调用两次   迭代。两个电话比一个电话贵得多   加上例外测试。特别是时间紧迫   for循环可以非常便宜地测试异常。

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

    •   
  •   

<强> 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());
}

在那个时间点true将是正确的答案。但是,随后的.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方法是最不易出错且最不模糊的方法。如果我没记错的话,python和es-6的人也有同样的想法。