Let me use the Luke Palmer memoization library: Data.MemoCombinators
import qualified Data.MemoCombinators as Memo import Data.Function (fix) -- we'll need this too
I am going to define things slightly different from how his library works, but basically it is the same thing (and, moreover, compatible). A “memoizable” thing takes itself as a contribution and produces a “real” thing.
type Memoizable a = a -> a
"memoizer" takes a function and creates its memoized version.
type Memoizer ab = (a -> b) -> a -> b
Let's write a little function to combine these two things. The Memoizable and Memoizer require a resulting memoized function.
runMemo :: Memoizer ab -> Memoizable (a -> b) -> a -> b runMemo memo f = fix (f . memo)
This is a bit of magic using the fix combinator. Do not pay attention to it; You can do it if you are interested.
So write a Memoizable version of a classic example:
fib :: Memoizable (Integer -> Integer) fib self = go where go 0 = 1 go 1 = 1 go n = self (n-1) + self (n-2)
Using the self convention makes code simple. Remember that self is what we expect, it is a memorialized version of this function itself, so recursive calls must be on self . Now run ghci.
ghci> let fib' = runMemo Memo.integral fib ghci> fib' 10000 WALL OF NUMBERS CRANKED OUT RIDICULOUSLY FAST
Now, about something about runMemo , you can create several fresh memoized versions of the same function, and they will not use memory banks. This means that I can write a function that is created locally and uses fib' , but then, as soon as fib' falls out of scope (or earlier, depending on the intelligence of the compiler) , it can be garbage collected . It does not need to be memorized at the upper level. This may or may not work well with memoization methods that rely on unsafePerformIO . Data.MemoCombinators uses a clean, lazy Trie that goes well with runMemo . Instead of creating an object that essentially becomes the memoization manager, you can simply create memoized functions on demand. The catch is that if your function is recursive, it should be written as Memoizable . The good news is that you can plug in any Memoizer you want. You can even use:
noMemo :: Memoizer ab noMemo f = f ghci> let fib' = runMemo noMemo fib ghci> fib' 30 -- wait a while; it computing stupidly 1346269