我注意到有时Haskell纯函数以某种方式被缓存:如果我用相同的参数调用该函数两次,第二次立即计算结果。
根据评论的要求,这是我在网上找到的一个例子:
isPrime a = isPrimeHelper a primes
isPrimeHelper a (p:ps)
| p*p > a = True
| a `mod` p == 0 = False
| otherwise = isPrimeHelper a ps
primes = 2 : filter isPrime [3,5..]
在运行它之前,我期待它非常慢,因为它一直在访问primes
的元素而没有显式地缓存它们(因此,除非这些值在某处缓存,否则它们需要重新计算很多次) 。但我错了。
如果我在GHCI中设置+s
(在每次评估后打印时间/记忆统计数据)并评估表达式primes!!10000
两次,这就是我得到的:
*Main> :set +s
*Main> primes!!10000
104743
(2.10 secs, 169800904 bytes)
*Main> primes!!10000
104743
(0.00 secs, 0 bytes)
这意味着必须缓存至少primes !! 10000
(或更好:整个primes
列表,因为primes!!9999
也不会花费时间)。
答案 0 :(得分:31)
primes
不是函数,而是一个常量,在haskellspeak中称为CAF。如果它使用了一个参数(例如,()
),如果调用它两次,你会得到同一个列表的两个不同版本,但由于它是一个CAF,你两次都会得到完全相同的列表;
作为ghci顶级定义,primes
永远不会变得无法访问,因此它指向的列表的头部(因此它的尾部/其余计算)从不被垃圾收集。添加参数可以防止保留该引用,然后列表将被垃圾收集,因为(!!)
向下迭代以找到正确的元素,而您对(!!)
的第二次调用将强制重复整个计算而不是仅仅遍历已经计算的列表。
请注意,在编译的程序中,没有像ghci那样的顶级范围,当最后一次引用它们时,事情会被垃圾收集,很可能在整个程序退出之前,CAF与否,这意味着你的第一次调用需要很长时间,第二个不会,之后,“程序的未来”不再引用CAF,CAF占用的内存将被回收。
primes package提供了一个参数(主要是,我声称)这个理由的函数,因为携带大约半TB的素数可能不是人们想要做的。
如果您想真正了解这一点,我建议您阅读STG paper。它不包括GHC的新发展,但是很好地解释了Haskell如何映射到汇编,以及通常如何通过严格性来吃掉thunk。