迭代器.不断地: 在Scala REPL中无法评估表达式

5

有人知道/解释为什么REPL在评估最后一个表达式时会卡住吗?奇怪的是,它没有抛出任何异常或其他东西,只是什么也没发生。

Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_40).

scala> val empty = Seq.empty[Int].iterator
empty: Iterator[Int] = empty iterator

scala> val emptyInfiniteIterator = Iterator.continually(empty).flatten

感谢您提前给予任何解释。
1个回答

6

以下是发生的情况。当您在Scala REPL中定义迭代器时,会打印有关该迭代器的一些信息,特别是它是否为空:

scala> Iterator.continually(List(1)).flatten
res1: Iterator[Int] = non-empty iterator

这些信息是由IteratortoString方法返回的,它的定义如下:

override def toString = (if (hasNext) "non-empty" else "empty")+" iterator"

基本上,在新创建的迭代器上调用hasNext。现在让我们看看在您的情况下hasNext在哪里起作用(scala.collection.TraversableOnce.FlattenOps#flatten):

  class FlattenOps[A](travs: TraversableOnce[TraversableOnce[A]]) {
    def flatten: Iterator[A] = new AbstractIterator[A] {
      val its = travs.toIterator
      private var it: Iterator[A] = Iterator.empty
      def hasNext: Boolean = it.hasNext || its.hasNext && { it = its.next().toIterator; hasNext }
      def next(): A = if (hasNext) it.next() else Iterator.empty.next()
    }
  }

啊哈!hasNext递归遍历迭代器,试图查找结束或单个非空元素。在你的情况下,永远不会发生,因为你有一个无限的空元素迭代器。所以,你有一个由REPL触发的无限循环。你没有得到StackOverflow,因为使用了尾递归,在scala中它被转译为while循环。

不错。它应该像(1 to 10).iterator span (_ < 5)中所看到的那样覆盖toString - som-snytt
@som-snytt,感谢您指出toString的问题。由于某种原因,我想象中REPL使用其他方式来漂亮地打印值。可能我有ammonite在脑海中,它不使用toString来处理标准类型。无论如何,现在所有的部分都已经到位了。 - Aivean
即使它以不同的方式覆盖了 toString,当您尝试使用迭代器进行任何操作时,仍将出现相同的无限循环... OP创建的是一个无限循环等待发生的情况,如果您理解 flatten 的含义,这一点应该是清楚的。 - Luigi Plinge
谢谢您的解释。小问题:从客户端代码的角度来看,这是否真的是预期的行为?我希望它会抛出一些异常。 - tkachuko
@tkachuko,在这种情况下到底发生了什么意外情况?在数学中,当你将零乘以无穷大时,会得到不确定性。当你创建这样的迭代器时,除非明确涵盖了这种特殊情况,否则一般规则适用,你会得到无限循环。而无限循环不会抛出任何异常,它们只是挂起。 - Aivean
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接