Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does this memoized fibonacci function work?

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?

like image 664
user42179 Avatar asked Mar 21 '13 09:03

user42179


2 Answers

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 for fiblist !! 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 calling fibm 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 call fibm which will look up those values in lower - and potentially already evaluated - positions of fiblist.

like image 135
Impredicative Avatar answered Nov 17 '22 00:11

Impredicative


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.

   Current expression                       Current evaluation state of fiblist

   fibm 3                                   <[ fibm x | x <- [0..] ]>

->   (simple expansion of the definition)

   fiblist !! (3-1) + fiblist !! (3-2)      <[ fibm x | x <- [0..] ]>

->   ((+) has to evaluate both its arguments to make progress, let's assume
     it starts with the left argument; (!!) traverses the list up to the given
     element and returns the element it finds)

   fibm 2 + fiblist !! (3-2)                <fibm 0> : <fibm 1> : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (simple expansion of the definition)

   (fiblist !! (2-1) + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : <fibm 1> : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (we again start with the first argument to (+),
     computing the result of (!!) does not cause any
     further evaluation of fiblist)

   (fibm 1 + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : >fibm 1< : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (expanding fibm 1 returns a result immediately;
     this concludes the computation of fibm 1,
     and the thunk is updated with the result)

   (1 + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : 1 : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (now we compute fiblist !! (2-2))

   (1 + fibm 0) + fiblist !! (3-2)          >fibm 0< : 1 : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (expanding fibm 0 returns 0 immediately, and the
     corresponding thunk can be updated)

   (1 + 0) + fiblist !! (3-2)               0 : 1 : >fibm 2< : <[fibm x | x <- [3..] ]>

->   (we can compute the (+), yielding the result of
     fibm 2; the corresponding thunk is updated)

   1 + fiblist !! (3-2)                     0 : 1 : 1 : <[fibm x | x <- [3..] ]>

->   (now the right argument of (+) has to be evaluated, but (!!)
     will return the already evaluated list element directly)

   1 + 1                                    0 : 1 : 1 : <[fibm x | x <- [3..] ]>

->   (arithmetic; note that as we called fibm 3 directly in the
     beginning, instead of fiblist !! 3, the list is unaffected
     by this final result)

   2                                        0 : 1 : 1 : <[fibm x | x <- [3..] ]>

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 to fibm will reuse already evaluated elements of the list. The list will eventually grow larger and larger, consuming more and more memory, though.

like image 5
kosmikus Avatar answered Nov 17 '22 00:11

kosmikus