【问题标题】:Scala 2.13 views vs LazyListScala 2.13 视图与 LazyList
【发布时间】:2021-11-17 10:09:23
【问题描述】:

我正在将一个项目从 Scala 2.12.1 迁移到 2.13.6,发现 SeqView#flatMap 现在返回一个 View,它没有 distinct 方法。因此,我有一段代码不再编译:

val nodes = debts.view
      .flatMap { case Debt(from, to, _) => List(from, to) }
      .distinct
      .map(name => (name, new Node(name)))
      .toMap

有一种愚蠢的方法可以解决这个问题,将视图转换为 seq,然后再转换回视图:

val nodes = debts.view
      .flatMap { case Debt(from, to, _) => List(from, to) }.toSeq.view
      .distinct
      .map(name => (name, new Node(name)))
      .toMap

但是,这显然不是很好,因为它会强制收集视图,而且必须在类型之间来回切换也非常不雅。我找到了另一种解决方法,即使用LazyList

val nodes = debts.to(LazyList)
      .flatMap { case Debt(from, to, _) => List(from, to) }
      .distinct
      .map(name => (name, new Node(name)))
      .toMap

现在这就是我想要的,它基本上表现得像一个 Java 流。当然,有些操作有 O(n) 的内存使用量,例如 distinct,但至少所有操作在流式传输之后,无需重构数据结构。

有了这个,我开始思考为什么我们应该需要一个视图,因为它们的功能远没有以前那么强大(即使我相信 2.13 已经修复了这个功能引入的一些其他问题)。我寻找答案并找到了提示,但没有发现足够全面的内容。以下是我的研究:

可能是我,但即使在阅读了这些参考资料后,我也没有发现使用视图有明显的好处,即使不是全部用例也是如此。还有比我更开明的人吗?

【问题讨论】:

  • 这看起来像是一个疏忽。 SeqView#flatMap 应该返回一个 SeqView,就像 Seq#flatMap 返回一个 Seq。您应该在 GitHub 中打开一个错误
  • 嗯,我认为这是因为与之前变得复杂的库相比,他们必须限制类型的精确度以简化库。我会考虑提交一个错误来看看!
  • 关于错误报告,有点类似于:github.com/scala/bug/issues/11159

标签: scala


【解决方案1】:

Scala 2.13 中的惰性序列实际上有 3 种基本可能性:View、Iterator 和 LazyList。

View 是最简单的惰性序列,附加成本极低。在一般情况下默认使用它是很好的,以避免在处理大型序列时分配中间结果。

可以多次遍历视图(使用 foreach、foldLeft、toMap 等)。每次遍历都会单独执行转换(map、flatMap、filter 等)。因此,必须注意避免耗时的转换,或者只遍历视图一次。

迭代器只能被遍历一次。它类似于 Java Streams 或 Python 生成器。 Iterator 上的大多数转换方法要求您只使用返回的 Iterator 并丢弃原始对象。

它也像 View 一样快速并支持更多操作,包括 distinct。

LazyList 基本上是一个真正的严格结构,可以动态自动扩展。 LazyList 记忆所有生成的元素。如果您有一个带有 LazyList 的 val,则将为所有生成的元素分配内存。但是如果你在运行中遍历它并且不存储在val 中,垃圾收集器可以清理遍历的元素。

Scala 2.12 中的 Stream 比 Views 或 Iterators 慢得多。我不确定这是否适用于 Scala 2.13 中的 LazyList。


所以每个惰性序列都有一些警告:

  • 视图可以多次执行转换。
  • 迭代器只能使用一次。
  • LazyList 可以为所有序列元素分配内存。

我认为在您的用例中,最合适的是 Iterator:

val nodes = debts.iterator
      .flatMap { case Debt(from, to, _) => List(from, to) }
      .distinct
      .map(name => (name, new Node(name)))
      .toMap

【讨论】:

  • 对,有道理!我没有考虑在这里使用迭代器,因为我对 Java 的想法很混乱,但它确实是最接近它的概念。我不太明白你对valLazyList 的评论,你能详细说明一下吗?
  • @Dici 来自 LazyList Javadoc:“人们必须小心记忆;如果你不小心,它会占用内存。这是因为 LazyList 的记忆创建了一个很像 @ 987654327@. 只要有东西抓住了头部,头部就会抓住尾部,以此类推。另一方面,如果没有任何东西抓住头部(例如,如果我们使用 def 来定义LazyList) 然后一旦不再直接使用,它就会消失。”
  • 好吧,val 你的意思是有人保留了对LazyList 的引用。那讲得通!我会接受答案。
猜你喜欢
  • 2019-12-18
  • 2021-09-29
  • 2023-03-12
  • 2020-05-05
  • 2019-12-21
  • 2021-08-26
  • 1970-01-01
  • 2022-01-10
  • 2020-02-22
相关资源
最近更新 更多