Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What functions are cached in Haskell?

I have the following code:

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'

(I am aware of that fibonacci implementation has exponential time complexity and does not use the cache)

The first time I execute fibmemo' 30 it takes 3 seconds, and the second time it takes ~0 seconds, because the result is cached. But the first version, fibmemo, does not get the result cached, it always takes 3 seconds to execute. The only difference is the definition (which as far as I know are equivalent).

So my question is, what functions are cached in Haskell?

I have already read https://wiki.haskell.org/Memoization and does not resolve my question.

like image 329
ManuelVS Avatar asked Oct 28 '18 10:10

ManuelVS


1 Answers

Essentially, the functions you defined behave as the following ones:

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

Why is fibMmemo' more efficient? Well, we can rewrite it as

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

which makes it more clear that the single list m gets created before n is taken as input. This means that all the calls to fibMemo' will use the same m. The first call evaluates a part of m slowly, and the successive calls will reuse that cached result (assuming the call hits the cache, of course, otherwise another part of m is evaluated and cached).

Instead, fibMemo is equivalent to

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

which takes the input n before the list m gets created. So, each call gets a new cache, which is pointless, since the whole purpose of a cache is that it is reused later.

The order of the lambda \n -> vs the let m = .. matters a lot in terms of the performance. Since m = .. does not use n, technically the let m = .. can be floated outwards, essentially turning fibMemo into fibMemo', without affecting the semantics. However, as you discovered, this does not preserve performance, in general!

This is indeed an optimization that GHC could perform, but does not, because it can easily make the performance significantly worse.

like image 154
chi Avatar answered Oct 04 '22 22:10

chi