Skip to content

Instantly share code, notes, and snippets.

@MattJOlson
Last active December 2, 2017 23:29
Show Gist options
  • Save MattJOlson/ff961fe99aca35b42163e8abb1391848 to your computer and use it in GitHub Desktop.
Save MattJOlson/ff961fe99aca35b42163e8abb1391848 to your computer and use it in GitHub Desktop.
In which Matt wonders about pointfree composition, and is enlightened

Okay, let's look at one of the Haskellbook end-of-chapter exercises from chapter 10.

myAny :: (a -> Bool) -> [a] -> Bool
myAny f xs = foldr (||) False $ map f xs
myAny f = foldr (||) False . map f
myAny = (foldr (||) False .) . map

First one's pretty obvious, right? But we want to go pointfree. (.) associates to the right, so in the second myAny, (. map f) is the last arg to the foldr. Let's make sure the types line up:

foldr (||) False is a [Bool] -> Bool -- makes sense, feed it a list and it'll poop out a Bool.

map f in this case has type [a] -> [Bool] -- sure, it'll run your predicate f on all the things.

(. map f) has type ([Bool] -> c) -> [a] -> c, though, and whoa what's going on.

Okay, break it down, this wasn't trivial for haskell tweeps so let's check it out. (.) has type (b -> c) -> (a -> b) -> a -> c, it's function composition, g . f. So the LHS of (. foo) is going to be a thing that can consume whatever foo produces. In this case, it's a thing that can consume a [Bool].

Now I get it; . map f is constrained to produce a list of Bools, and the type declaration for myAny (or foldr, really) tells us that it has to produce a Bool. This is where I'd probably stop if I was writing production code.

Now let's look at that last one. Again, (.) associates to the right, so again we'll look at (. map) and figure out how to plumb it in. Wow, that was not what I expected:

(. map) :: (([a] -> [b]) -> c) -> (a -> b) -> c

What the fuck. Okay, makes sense with currying. map takes a function a -> b and spits out a function that will transform your list of as to a list of bs. So we want to glue this together with foldr (||) False, which is a [Bool] -> Bool How do I get from there to my desired lhs type (([a] -> [Bool]) -> bool)? Well, (.) seems to be pretty good at gluing stuff onto the lhs type. So:

(foldr (||) False .) :: (a -> [Bool]) -> a -> Bool

(Okay, it's on Foldable, but lists for now.) That says "give me a way to make a list of Bools from a thing, and also a thing, and I'll give you a Bool. And hey! It also looks a lot like the LHS to (. map) if you let that signature's c resolve to [a]. And indeed:

(foldr (||) False .) . map :: (a -> Bool) -> [a] -> Bool

So that's fun and all, and probably worth noodling around with for a better understanding of composition, but waaaaaay too cute to check into a repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment