如何判断Haskell是缓存结果还是重新计算结果?

peo*_*oro 33 caching haskell function

我注意到有时Haskell纯函数以某种方式被缓存:如果我用相同的参数调用函数两次,第二次立即计算结果.

  1. 为什么会这样?它是GHCI功能还是什么?
  2. 我能依靠这个(即:我能确定地知道是否会缓存一个函数值)?
  3. 我可以为某些函数调用强制或禁用此功能吗?

根据评论的要求,这是我在网上找到的一个例子:

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..]
Run Code Online (Sandbox Code Playgroud)

在运行它之前,我期望它非常慢,因为它一直在访问元素而primes没有显式地缓存它们(因此,除非这些值在某处缓存,否则它们需要重新计算很多次).但是我错了.

如果我设置+sGHCI(在每次评估后打印时间/内存统计数据)并评估表达式primes!!10000两次,这就是我得到的:

*Main> :set +s
*Main> primes!!10000
104743
(2.10 secs, 169800904 bytes)
*Main> primes!!10000
104743
(0.00 secs, 0 bytes)
Run Code Online (Sandbox Code Playgroud)

这意味着必须缓存至少primes !! 10000(或更好:整个primes列表,因为也primes!!9999不会花费时间).

bar*_*oap 31

primes在你的代码中,不是一个函数,而是一个常量,在haskellspeak中被称为CAF.如果它采用了一个参数(例如()),如果调用它两次,你会得到同一个列表的两个不同版本,但由于它是一个CAF,所以你得到完全相同的列表两次;

作为ghci顶级定义,primes永远不会变得无法访问,因此它指向的列表的头部(因此它的尾部/其余计算)从不被垃圾收集.添加参数可以防止保留该引用,然后将列表进行垃圾收集,(!!)向下迭代以找到正确的元素,第二次调用(!!)将强制重复整个计算而不是仅遍历已经计算的列表.

请注意,在编译的程序中,没有像ghci那样的顶级范围,当最后一次引用它们时,事情就会被垃圾收集,很可能在整个程序退出之前,CAF与否,这意味着你的第一次调用需要很长时间,第二个没有,之后,"你的程序的未来"不再引用CAF,CAF占用的内存被回收.

素数包提供了一个函数,它的论据(主要是,我会要求)正是由于这个原因,围绕素数的一半TB的携带可能不是一个想要做什么.

如果你想真正深入了解这一点,我建议你阅读STG论文.它不包括GHC的新发展,但是很好地解释了Haskell如何映射到汇编,以及通常如何严格地吃掉thunk.

  • @peoro:http://conal.net/blog/posts/everything-is-a-function-in-haskell/ (5认同)