我能够编写一个流处理功能drop(n,s)
,该功能可以扩展到非常大的流。但是,当我编写另一个函数nth(n,s)
时,它接受了一个流s
并将其转发到drop(n,s)
时,似乎nth()
正在“保持住”流。这样会导致OutOfMemoryError
等于大n
。
代码如下:
import scala.annotation.tailrec
import scala.collection.immutable.Stream.cons
object Streams {
def iterate[A](start: A, f: A => A): Stream[A] =
cons(start, iterate(f(start), f))
def inc(n:Int) = n + 1
def naturals() =
iterate(1, inc)
@tailrec def drop[T](n : Int, s : Stream[T]) : Stream[T] =
if (n <= 0 || s.isEmpty) s
else drop(n-1, s.tail)
// @inline didn't seem to help
def nth[T](n : Int, s : Stream[T]) =
drop(n,s).head
def N = 1e7.toInt
def main(args: Array[String]) {
println(drop(N,naturals()).head) // works fine for large N
println(nth(N, naturals())) // results in OutOfMemoryError for N set to 1e7.toInt and -Xmx10m
}
}
我在以下Java问题上的经验:why does this Java method leak—and why does inlining it fix the leak?使我相信Scala为nth()
生成的代码在调用{{1}之前清除s
时不够积极。 }。 Clojure库技巧(Java技巧)(请参见链接的问题)在这里不起作用,因为所有Scala函数参数均为drop()
(而非val
),因此无法进行分配({{ 1}})。
如何用var
来编写可扩展的null
?
以下是2009-2011年间与Scala编译器相关的错误(nth()
是根据drop()
实现的):https://issues.scala-lang.org/browse/SI-2239
我无法从该Scala错误票证中得知他们是如何解决的。故障单中有一个建议,建议解决此问题的唯一方法是在reduceLeft()
中复制foldLeft()
代码。我真的希望那不是答案。
更新:Andrey Tyukin的答案https://stackoverflow.com/a/52209383/156550对其进行了修复。现在我有:
foldLeft()
并且reduceLeft()
可以很好地缩放。
答案 0 :(得分:2)
这里是一种快速脏污的解决方案,只需要增加两个字符:
def nth[T](n : Int, s: => Stream[T]) = drop(n,s).head
在没有=>
的情况下会发生以下情况:
s
作为参数传递给nth
时,它是对naturals()
先前创建的已经存在的值的引用。 .head
,drop(n, s)
必须将流返回到nth
的堆栈帧,因此nth
的堆栈帧不能被丢弃,因此{ {1}}保留引用nth
。s
工作时,对参数s
的引用由nth
的堆栈帧保留,因此所有丢弃的前缀实际上无法被垃圾收集(这是因为{ {1}}保证,如果您按住它的头部并多次迭代,它将返回相同的结果。)现在,如果您添加drop
,则会发生以下情况:
Stream
,=>
的堆栈帧仍然无法丢弃nth
不保留对传递给.head
的流的开头的引用。它仅保留对产生nth
的 thunk 的引用。附加说明(Dima的测试用例):
请注意,如果thunk本身仅返回对已经存在的drop
的引用,则其行为仍然与没有Stream
时的行为相同。例如,如果您的Stream
定义如下:
=>
然后调用
inc
只会打印当前时间十次(而不是15次)。