I'm trying to understand something about Haskell functions.
First, here is a Fibonacci function defined in the typical "slow" way (i.e. recursive with no memoization, and no infinite-list tricks)
slowfib :: Int -> Integer
slowfib 0 = 0
slowfib 1 = 1
slowfib n = slowfib (n-2) + slowfib (n-1)
Next, a canonical memoizing version of the same. (Only slightly different from typical examples in tutorals/books/etc, because I prefer the prefix version of the !!
operator.)
memfib = (!!) (map fib [0..])
where
fib 0 = 0
fib 1 = 1
fib k = memfib(k-2) + memfib(k-1)
The above solution uses partial application of the !!
operator, which makes sense: we want memfib
to end up as a function that takes a parameter, and we are defining it without including a parameter in the definition.
So far so good. Now, I thought I could write an equivalent memoizing function that does include a parameter in the definition, so I did this:
memfib_wparam n = ((!!) (map fib [0..])) n
where
fib 0 = 0
fib 1 = 1
fib k = memfib_wparam(k-2) + memfib_wparam(k-1)
(In Lambda calculus terms, memfib
and memfib_wparams
are just eta-conversions of each other. I think???)
This works, but the memoization is gone. In fact, memfib_wparam
behaves even worse than showfib
: Not only is it slower, but its memory usage is more than double.)
*Main> slowfib 30
832040
(1.81 secs, 921,581,768 bytes)
*Main> memfib 30
832040
(0.00 secs, 76,624 bytes)
*Main> memfib_wparam 30
832040
(2.01 secs, 2,498,274,008 bytes)
What's going on here? More importantly, what is my broader understanding of Haskell function definitions getting wrong? I was assuming the syntax I used in memfib_wparam
was just syntactic sugar for what I did in memfib
, but clearly it isn't.