8

The following example of a function using memoization is presented on this page:

memoized_fib :: Int -> Integer
memoized_fib = (map fib [0..] !!)
    where fib 0 = 0
          fib 1 = 1
          fib n = memoized_fib (n-2) + memoized_fib (n-1)

What if we wanted to memoize a multi-parameter function, though? We could create a 'multiplied Fibonacci', for example, that would be defined f(m,n) = m*f(m,n-2) + m*f(m,n-1). I modified the code above for this 'multiplied Fibonacci' function as follows:

mult_fib :: Integer -> Int -> Integer
mult_fib mult = (map (m_fib mult) [0..] !!)
    where m_fib _ 0 = 0
          m_fib _ 1 = 1
          m_fib m n = m*(mult_fib m (n-2)) + m*(mult_fib m (n-1))

The runtime of the modified function is exponential, even though the original is linear. Why does this technique not work in the second case? Also, how could the function be modified to make use of memoization (without using library functions)?

5
  • 1
    In this case you can just remove m_fib's first argument,replace m by mult and mult_fib m by m_fib. In a sense, this would define a family of memoized functions indexed by mult. Commented Aug 19, 2013 at 0:32
  • @Vitus: right, though it doesn't quite work like that (recursing on the un-memoised m_fib would destroy the performance advantage). Commented Aug 19, 2013 at 0:49
  • @leftaroundabout: Right, I forgot the one layer of indirection which is your multFib', sorry. Commented Aug 19, 2013 at 1:39
  • So the issue is that GHC can't (or shouldn't?) recognize that mult_fib mult and mult_fib m are the same partially applied functions? Commented Aug 19, 2013 at 22:32
  • @JohnG: This is one of the things you usually do not want to recognize because it can lead to space leaks; showing that this transformation doesn't introduce such leak is much harder task indeed. Also, you do not want to depend on optimizations in this case; you want the memoization to be reliable. It's better to be explicit about the sharing and have a guarantee that your code will do the right thing no matter the optimizations. Commented Aug 20, 2013 at 0:46

2 Answers 2

8

As said by Vitus, in your example in can be done quite simply. The correct implementation of this idea:

multFib :: Integer -> Int -> Integer
multFib mult = multFib'
    where multFib' = (map m_fib [0..] !!)
          m_fib 0 = 0
          m_fib 1 = 1
          m_fib n = mult * (multFib' $ n-2) + mult * (multFib' $ n-1)

However, the memoisation is not quite as powerful as in your example here: it'll only serve to optimise single function calls, but the result list will in general not be stored between subsequent calls of multFib with the same mult argument.

To achieve that, you'll need to index your memisation lookup on both arguments, like so:

multFibFullMemo :: Int -> Int -> Integer
multFibFullMemo = \mult n -> memo_table !! mult !! n
 where memo_table = [ [m_fib mult' n' | n'<-[0..]] | mult' <- [0..] ]
       m_fib _ 0 = 0
       m_fib _ 1 = 1
       m_fib mult n = m * (multFibFullMemo mult $ n-2) + m * (multFibFullMemo mult $ n-1)
        where m = toInteger mult

Quite obviously, this won't be efficient if you intend to use it with larger mult arguments: it'll always need to skip to a list with the length of that number.

More sophisticated memoisation that doesn't suffer from such issues is provided by libraries such as MemoTrie.

Sign up to request clarification or add additional context in comments.

3 Comments

As a rule of thumb, GHC will evaluate any given expression at most once every time the enclosing lambda is entered. If you leave the lambda and enter it again with the same value later, the expression can (and usually will) be recomputed.
Anyways, since it looks like my two edits got merged together and only the last edit message remains: the original list comprehension was inside the \mult n -> lambda so it was recomputed everytime you called multFibFullMemo.
As i understood, to get memoized something we have to put it in at the top of lamda expression. And make all computations inside this lambda. It seems like play with compiler features.
1

Here is a version using MemoTrie:

import Data.MemoTrie

multFib :: Integer -> Int -> Integer          
multFib = memo2 multFib'
  where multFib' m 0 = 0
        multFib' m 1 = 1
        multFib' m n = m * (multFib m (n-2)) + m * (multFib m (n-1))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.