Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial application versus pattern matching: why do these Haskell functions behave differently?

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.

like image 836
cschatz Avatar asked Feb 21 '20 03:02

cschatz


People also ask

How does Haskell pattern matching work?

Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns. When defining functions, you can define separate function bodies for different patterns.

What is a partial function Haskell?

From HaskellWiki. A partial function is a function that is not defined for all possible arguments of the specified type. Examples in the Haskell standard library are: head , tail : undefined for empty lists. (!!) : undefined if the index is at least as big as the list length.

How do you define a function in Haskell?

The most basic way of defining a function in Haskell is to ``declare'' what it does. For example, we can write: double :: Int -> Int double n = 2*n. Here, the first line specifies the type of the function and the second line tells us how the output of double depends on its input.

What are guards in Haskell?

Haskell guards are used to test the properties of an expression; it might look like an if-else statement from a beginner's view, but they function very differently. Haskell guards can be simpler and easier to read than pattern matching .


1 Answers

The difference is in when your fib function is bound.

where-bound definitions have access to the outer function's parameters (i.e. the parameters are "in scope" within where). This means that fib should have access to n, which in turn means that fib is defined after n is passed, which means it's a different fib for every n, which means it's a different call to map fib [0..] for every n.

If you wanted to eta-expand your memfib, this would be the "right" way to do it (i.e. without unduly expanding the scope of n):

memfib = \n -> theCache !! n
    where
        theCache = map fib [0..]

        fib 0 = 0 
        fib 1 = 1 
        fib k = memfib(k-2) + memfib(k-1)

If you're comparing with lambda calculus, the key difference is that eta-reduction/expansion doesn't say anything about performance, it just guarantees that the result of the program stays the same logically. Which it does.

like image 118
Fyodor Soikin Avatar answered Sep 29 '22 06:09

Fyodor Soikin