【问题标题】:Why does this Iterator infinitely loop?为什么这个迭代器无限循环?
【发布时间】:2020-09-01 21:02:54
【问题描述】:

我正在尝试链接Iterators

var it = Iterator(1)
it.next
it = Iterator(2) ++ it
it.next
it.hasNext

这在hasNext 上无限循环,您可以在此处看到:https://scastie.scala-lang.org/qbHIVfsFSNO5OYmT4pkutA

如果你运行它并在堆栈无限循环时检查堆栈,它会在内容循环中循环:

        at scala.collection.Iterator$ConcatIterator.merge(Iterator.scala:213)
        at scala.collection.Iterator$ConcatIterator.advance(Iterator.scala:197)
        at scala.collection.Iterator$ConcatIterator.hasNext(Iterator.scala:227)

(此堆栈来自 Scala 2.12.11,但 Scastie 链接在 2.13.2 中显示相同的行为)。

我知道在迭代器上调用方法后永远不应该使用它,但这似乎对我有用。使用var 指向“当前”迭代器并将其更改为指向一个新的迭代器,该迭代器附加前一个迭代器的其余部分。

以下轻微修改确实有效:

var it = Iterator(1)
it.next
val x = it
it = Iterator(2) ++ x
it.next
it.hasNext

Scastie 链接:https://scastie.scala-lang.org/1X0jslb8T3WIFLHamspYAg

这向我表明,以某种方式损坏的版本正在创建一个附加自身的迭代器。关于这里发生了什么的任何提示?

【问题讨论】:

  • 总之,可变性不好。
  • 这里没有异议。我正在将此与功能纯代码进行基准测试,如果可能的话,我会选择后者。唯一的问题是这是性能关键代码,因此如果它运行得非常快,它是一个合理的潜在突变候选者。令人惊讶的是,在 5 行命令式代码中会有多少混乱......

标签: scala mutability pass-by-name


【解决方案1】:

Iterator++ 方法的参数是按名称传递的。 ++ 返回一个新的 Iterator,它只存储一个返回 it 的函数,但在您尝试使用附加元素之前不会调用它。

所以++ 仅在您调用it.hasNext 时才尝试评估参数,但到那时it 已经被重新定义为++ 的结果,因此它最终尝试将it 附加到自身.

换句话说,vars 和 by-name 参数不能一起使用。

所以不要将Iterator 方法结果重新分配给同一个变量,而是给它们新的名称:

val it = Iterator(1)
it.next
val it2 = Iterator(2) ++ it
it2.next
it2.hasNext

【讨论】:

  • 我不知道 by-name 会捕获实际的 var 而不仅仅是它指向的对象,但这是有道理的。谢谢!
  • @JackKoenig 是的,按名称传递会创建一个闭包,而闭包确实会在范围内看到对 vars 的更改。例如:def make(): () => Int = { var i = 0; def result() = { i += 1; i }; result }; val f = make(); println(f(), f(), f()) 将打印 (1,2,3)
猜你喜欢
  • 2020-08-08
  • 2013-08-10
  • 1970-01-01
  • 2018-03-17
  • 1970-01-01
  • 2019-03-26
  • 2020-07-12
  • 2013-07-25
  • 2012-09-24
相关资源
最近更新 更多