Haskell 中缓存了哪些函数?

What functions are cached in Haskell?

我有以下代码:

memoize f = (map f [0 ..] !!)

fib' 0 = 1
fib' 1 = 1
fib' n = fib' (n - 1) + fib' (n - 2)

fibMemo n = memoize fib' n
fibMemo'  = memoize fib'

(我知道斐波那契实现具有指数时间复杂度并且不使用缓存)

我第一次执行 fibmemo' 30 需要 3 秒,第二次需要 ~0 秒,因为结果被缓存了。但是第一个版本,fibmemo,没有得到缓存的结果,它总是需要 3 秒来执行。唯一的区别是定义(据我所知是等效的)。

所以我的问题是,Haskell中缓存了哪些函数?

我已经阅读了 https://wiki.haskell.org/Memoization 但没有解决我的问题。

本质上,您定义的函数的行为如下:

fibMemo n = let m = map fib' [0..] in m !! n
fibMemo'  = let m = map fib' [0..] in (m !!)

为什么 fibMmemo' 更有效率?好吧,我们可以将其重写为

fibMemo'  = let m = map fib' [0..] in \n -> m !! n

这使得在 n 作为输入之前创建单个列表 m 变得更加清楚。这意味着对 fibMemo' 的所有调用都将使用相同的 m。第一次调用缓慢地评估 m 的一部分,后续调用将重用缓存的结果(当然,假设调用命中缓存,否则 m 的另一部分被评估和缓存)。

相反,fibMemo 等同于

fibMemo = \n -> let m = map fib' [0..] in m !! n

在创建列表 m 之前接受输入 n。所以,每次调用都会得到一个新的缓存,这是没有意义的,因为缓存的全部目的是稍后重用它。

lambda \n ->let m = .. 的顺序在性能方面非常重要。由于 m = .. 不使用 n,因此从技术上讲 let m = .. 可以向外浮动,实质上将 fibMemo 变成 fibMemo',而不影响语义。但是,正如您所发现的,这通常不会保持性能!

这确实是 GHC 可以执行但没有执行的优化,因为它很容易使性能显着变差。