Search code examples
haskellpartial-applicationequational-reasoning

How does Haskell evaluate this function defined with partial application?


I'm trying to understand how Haskell evalutes pp1 [1,2,3,4] to get [(1,2),(2,3),(3,4)] here:

1. xnull f [] = []
2. xnull f xs = f xs
3. (/:/) f g x = (f x) (g x)
4. pp1 = zip /:/ xnull tail

I start like this:

a)  pp1 [1,2,3,4] = (zip /:/ xnull tail) [1,2,3,4]  -- (rule 4).
b)  (zip /:/ xnull tail) [1,2,3,4] 
       = (zip (xnull [1,2,3,4]) (tail) [1,2,3,4])   -- (rule 3)
c)  -- I'm not sure how to continue because xnull receives a function 
    -- and a param, and in this case it only receives a param.

Any help?


Solution

  • Just keep expanding:

    pp1 [1, 2, 3, 4] = zip /:/ xnull tail $ [1, 2, 3, 4]
                     -- inline (/:/) f g x = f x (g x)
                     --  f = zip, g = xnull tail, x = [1, 2, 3, 4]
                     -- therefore: 
                     = zip [1, 2, 3, 4] (xnull tail $ [1, 2, 3, 4])
                     -- inline the definition of xnull and note that the argument is not null
                     -- so we just want f x, or tail [1, 2, 3, 4]
                     = zip [1, 2, 3, 4] (tail [1, 2, 3, 4])
                     -- evaluate tail
                     = zip [1, 2, 3, 4] [2, 3, 4]
                     -- evaluate zip
                     = [(1, 2), (2, 3), (3, 4)]
    

    Operator presidence matters. You didn't specify the associativity of (/:/) so it was defaulted to be relatively weak. Therefore, (xnull tail) bound tighter than (/:/).

    Also, as a side note, (/:/) already exists in the standard library as (<*>) from Control.Applicative. It's sufficiently general so this might be difficult to see, but the Applicative instance for Reader (or perhaps better understood as the function Applicative) provides this exact instance. It's also known as ap from Control.Monad.

    zip <*> tail :: [b] -> [(b, b)]
    zip <*> tail $ [1, 2, 3, 4] = [(1, 2), (2, 3), (3, 4)]