Scala 2.13 视图与 LazyList
Scala 2.13 views vs LazyList
我正在将一个项目从 Scala 2.12.1 迁移到 2.13.6,发现 SeqView#flatMap
现在 returns 一个 View
,它没有 distinct
方法。因此,我有一段代码不再编译:
val nodes = debts.view
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap
有一种愚蠢的方法可以解决这个问题,将视图转换为序列,然后再返回视图:
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 已经解决了这个功能引入的一些其他问题)。我寻找答案并找到了提示,但没有找到足够全面的提示。以下是我的研究:
可能是我,但即使在阅读了这些参考资料之后,对于大多数(如果不是全部)用例,我也没有发现使用视图有明显的好处。还有比我开明的吗?
实际上在 Scala 2.13 中惰性序列有 3 种基本可能性:View、Iterator 和 LazyList。
View 是最简单的惰性序列,附加成本很少。一般情况下最好使用默认值,以避免在处理大型序列时分配中间结果。
可以多次遍历View(使用foreach、foldLeft、toMap等)。每次遍历都会单独执行转换(map、flatMap、filter 等)。因此必须小心,要么避免耗时的转换,要么只遍历视图一次。
Iterator只能遍历一次。它类似于 Java 流或 Python 生成器。 Iterator 上的大多数转换方法都要求您仅使用返回的 Iterator 并丢弃原始对象。
它也像 View 一样快,并且支持更多操作,包括 distinct。
LazyList 基本上是一个真正的严格结构,可以动态自动扩展。 LazyList 记忆所有生成的元素。如果你有一个带有 LazyList 的 val
,内存将被分配给所有生成的元素。但是如果你动态遍历它并且不存储在val
中,垃圾收集器可以清理遍历的元素。
Scala 2.12 中的 Stream 比视图或迭代器慢得多。我不确定这是否适用于 Scala 2.13 中的 LazyList。
所以每个惰性序列都有一些警告:
- 视图可以多次执行转换。
- 迭代器只能使用一次。
- LazyList 可以为所有的序列元素分配内存。
我认为在您的用例中,迭代器是最合适的:
val nodes = debts.iterator
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap
我正在将一个项目从 Scala 2.12.1 迁移到 2.13.6,发现 SeqView#flatMap
现在 returns 一个 View
,它没有 distinct
方法。因此,我有一段代码不再编译:
val nodes = debts.view
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap
有一种愚蠢的方法可以解决这个问题,将视图转换为序列,然后再返回视图:
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 已经解决了这个功能引入的一些其他问题)。我寻找答案并找到了提示,但没有找到足够全面的提示。以下是我的研究:
可能是我,但即使在阅读了这些参考资料之后,对于大多数(如果不是全部)用例,我也没有发现使用视图有明显的好处。还有比我开明的吗?
实际上在 Scala 2.13 中惰性序列有 3 种基本可能性:View、Iterator 和 LazyList。
View 是最简单的惰性序列,附加成本很少。一般情况下最好使用默认值,以避免在处理大型序列时分配中间结果。
可以多次遍历View(使用foreach、foldLeft、toMap等)。每次遍历都会单独执行转换(map、flatMap、filter 等)。因此必须小心,要么避免耗时的转换,要么只遍历视图一次。
Iterator只能遍历一次。它类似于 Java 流或 Python 生成器。 Iterator 上的大多数转换方法都要求您仅使用返回的 Iterator 并丢弃原始对象。
它也像 View 一样快,并且支持更多操作,包括 distinct。
LazyList 基本上是一个真正的严格结构,可以动态自动扩展。 LazyList 记忆所有生成的元素。如果你有一个带有 LazyList 的 val
,内存将被分配给所有生成的元素。但是如果你动态遍历它并且不存储在val
中,垃圾收集器可以清理遍历的元素。
Scala 2.12 中的 Stream 比视图或迭代器慢得多。我不确定这是否适用于 Scala 2.13 中的 LazyList。
所以每个惰性序列都有一些警告:
- 视图可以多次执行转换。
- 迭代器只能使用一次。
- LazyList 可以为所有的序列元素分配内存。
我认为在您的用例中,迭代器是最合适的:
val nodes = debts.iterator
.flatMap { case Debt(from, to, _) => List(from, to) }
.distinct
.map(name => (name, new Node(name)))
.toMap