Haskell中缓存了哪些功能?

时间:2018-10-28 10:29:40

标签: haskell caching memoization

我有以下代码:

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,但无法解决我的问题。

1 个答案:

答案 0 :(得分:4)

本质上,您定义的功能的行为如下:

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

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

相反,fibMemo等同于

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

,它在创建列表n之前接受输入m。因此,每个调用都会获得一个新的缓存,这毫无意义,因为缓存的全部目的是稍后再使用。

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

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