Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Function Composition with Map Function

I'm going through the Richard Bird's "Thinking Functionally with Haskell" book and there is a section that I can't understand where he's proving a property of the filter method. What he's proving is:

filter p . map f = map f . filter (p . f)

Previously in the book, he defines the filter as:

filter p = concat . map (test p)
test p x = if p x then [x] else []

This is how he proves the first equation:

    filter p . map f
= {second definition of filter} -- He's referring to the definition I gave above
    concat . map (test p) . map f
= {functor property of map}
    concat . map (test p . f)
= {since test p . f = map f . test (p . f)}
    concat . map (map f . test (p . f))
= {functor property of map}
    concat . map (map f) . map (test (p . f))
= {naturality of concat}
    map f . concat . map (test (p . f))
= {second definition of filter}
    map f . filter (p . f)

What I can't understand is how test p . f is equal to map f . test (p . f).

This is how I tried to test it:

test :: (a -> Bool) -> a -> [a]
test p x = if p x then [x] else []

test ((<15) . (3*)) 4 -- test p .f, returns [4]
(map (3*) . test((<15) . (3*))) 4 -- map f . test (p . f), returns [12]

Can anyone please explain what I'm missing here?

like image 972
flopoe Avatar asked Dec 23 '22 03:12

flopoe


2 Answers

You tested

test (p . f) = map f . test (p . f)

Which is indeed false. The property is actually

test p . f = map f . test (p . f)

Where the LHS associates as

test p . f = (test p) . f

Remember, function application is more tightly binding than any user-definable operator, acting like it's infixl 10. Two identifiers next to each other are always part of a prefix function application. (Except in the case of as-patterns: f xs@ys zs means f (xs@ys) zs.)

To prove the property:

    test p . f
={definition of (.)}
    \x -> test p (f x)
={definition of test}
    \x -> if p (f x) then [f x] else []
={definition of map, multiple times}
    \x -> if p (f x) then map f [x] else map f []
={float map f out of cases}
    \x -> map f (if p (f x) then [x] else [])
={definition of (.)}
    \x -> map f (if (p . f) x then [x] else [])
={definition of test}
    \x -> map f (test (p . f) x)
={definition of (.)}
    map f . test (p . f)

Adapting your example, test (<15) . (*3) means "multiply by 3, ensuring that the result is less than 15." map (*3) . test ((<15) . (*3)) means "ensure that thrice the input is less than 15, and, if so, return thrice the input."

like image 161
HTNW Avatar answered Dec 25 '22 23:12

HTNW


HTNW's answer covers your test case and how to prove the equation using the definition of test. I'd say there still is an underlying question, though: from which hat were we supposed to pull that equation of -- why should we even consider the possibility of it being true? To answer that, let's begin by having a second look at the equation:

test p . f = map f . test (p . f)

In words, it says that modifying a value using some f function and then, given some suitable predicate p, applying test p on it is the same than modifying the value after using test (with the predicate suitably modified by composing it with f, so that it fits the type of the unmodified value).

Next, let's consider the type of test:

-- I'm adding the implicit forall for the sake of emphasis.
forall a. (a -> Bool) -> a -> [a]

The crucial thing here is that a function with this type must work for any choice of a whatsoever. If it can be anything, we don't know anything about it in advance when implementing a function with this type, like test. That severely limits what such a function can do: in particular, the elements of the result list, if any, must all be the same as the supplied value of type a (how could we change it into something else without knowing its type in advance?), and the predicate must be either ignored or applied to that same value (what else would we apply it to?). Taking that into account, what the equation says now feels natural: it doesn't matter whether we change the values with f before or after test, because test won't change the values on its own.

One way to make this sort of reasoning rigorous is through free theorems. A free theorem for a type is, thanks to parametric polymorphism, guaranteed to always hold for any possible value of that type, and you don't need anything other than the type to figure it out. It happens that the free theorem for forall a. (a -> Bool) -> a -> [a] is precisely test p . f = map f . test (p . f). Since I can't do justice to the subject in these short lines, here are a few references about free theorems:

  • Parametricity: Money for Nothing and Theorems for Free, by Bartosz Milewski, is a good exposition with pointers to the key primary sources if you want to dig deeper.

  • What Does fmap Preserve?, by yours truly, is not exactly about free theorems, but it presents some of the related themes in (hopefully) an accessible way.

  • Relevant Stack Overflow posts: amalloy's answer to Polymorphic reasoning; Type signatures that never make sense.

  • lambdabot can generate free theorems for you. You can use it either as a command line tool or through the bot that runs on the #haskell Freenode IRC channel.

like image 40
duplode Avatar answered Dec 26 '22 00:12

duplode