In the current exercise assignment of the Functional Programming course I'm doing, we've got to make a memoized version of a given function. To explain memoization, the following example is given:
fiblist = [ fibm x | x <- [0..]]
fibm 0 = 0
fibm 1 = 1
fibm n = fiblist !! (n-1) + fiblist !! (n-2)
But I don't fully understand how this works.
Let's call fibm 3
.
fibm 3
--> fiblist !! 2 + fibList 1
--> [fibm 0, fibm 1, fibm 2] !! 2 + [fibm 0, fibm 1] !! 1
--> fibm 2 + fibm 1
--> (fiblist !! 1 + fiblist 0) + 1
--> ([fibm 0, fibm 1] !! 1 + [fibm 0] !! 0) + 1
--> (fibm 1 + fibm 0) + 1
--> 1 + 0 + 1
--> 2
From other questions/answers and googling I learned that somehow, the evaluated fiblist is shared between calls.
Does this mean that, for example, for fiblist !! 2 + fiblist !! 1
, the list values are only calculated once for fiblist !! 2
and then just reused for fiblist !! 1
?
Then the two fibonacci numbers are calculated only once per call, so no exponential number of calls. But what about the "lower" levels of the call in the fiblist
function? How do they benefit from the calculated fiblist
in the original fibm
call?
Let's walk through the evaluation step by step. In addition to showing the current expression, we show the current state of evaluation of
fiblist
in memory. There, I write<expr>
to denote an unevaluated expression (often called a thunk), and>expr<
to denote an unevaluated expression that is currently under evaluation. You can see lazy evaluation in action. The list gets evaluated only as far as demanded, and subcomputations that complete will be shared for future reuse.As
fiblist
is a global constant (often called a CAF for "constant applicative form"), the partially evaluated state of the list will persist and future calls tofibm
will reuse already evaluated elements of the list. The list will eventually grow larger and larger, consuming more and more memory, though.The crucial part here is that the list is lazily evaluated, which means that the element isn't computed until the first time it's requested. Once it has been evaluated, however, it's there for anything else to look up. So in your example, you're right in saying that the values are only calculated once for the
fiblist !! 2
and then reused forfiblist !! 1
.The 'lower levels' of the fiblist function work in the same way. The first time I call
fiblist !! 1
it will be evaluated by callingfibm 1
, which is just 1, and this value will then remain in the list. When you try to get up higher Fibonacci number,fiblist
will callfibm
which will look up those values in lower - and potentially already evaluated - positions offiblist
.