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

  • 为什么会发生这种情况?它是GHCI功能还是什么?
  • 我可以依靠它吗(即:我可以确定地知道是否将缓存一个函数值)吗?
  • 我可以对某些函数调用强制或禁用此功能吗?


  • 根据评论的要求,以下是我在网上找到的示例:
    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也不会花费时间)。

    最佳答案

    在您的代码中,primes不是函数,而是常量(在haskellspeak中称为CAF)。如果使用一个参数(例如()),则两次调用同一列表,您将获得两个不同版本的列表,但由于它是一个CAF,因此两次都获得了完全相同的列表。

    作为ghci的顶级定义,primes永远不会变得无法访问,因此它所指向的列表的开头(以及它的尾部/计算的其余部分)永远不会被垃圾回收。添加参数会阻止保留该引用,然后列表将被垃圾回收,因为(!!)对其进行迭代以找到合适的元素,并且您对(!!)的第二次调用将强制重复整个计算,而不仅仅是遍历已计算的列表。

    请注意,在已编译程序中,没有像ghci中那样的顶级作用域,并且在最后一次引用它们时,很可能在整个程序退出之前(无论是否存在CAF),都会收集垃圾,这意味着您的第一次调用将花费很长时间,第二个则不行,此后,“程序的 future ”不再引用CAF,则CAF占用的内存将被回收。

    primes package提供了一个接受参数的函数(主要是出于我的主张),因为这个原因可能是,携带大约TB的质数可能不是我们想要的。

    如果您想深入了解本章,建议阅读STG paper。它不包括GHC的最新进展,但是在解释Haskell如何映射到装配体上以及在总体上如何严格地吃掉笨蛋方面做得很好。

    关于function - 如何判断Haskell将缓存结果还是重新计算结果?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5032750/

    10-12 02:08