Skip to content

Instantly share code, notes, and snippets.

@ebiggs
Last active December 13, 2015 21:58
Show Gist options
  • Save ebiggs/4981393 to your computer and use it in GitHub Desktop.
Save ebiggs/4981393 to your computer and use it in GitHub Desktop.
Composing composition in haskell (.).(.) is an interesting and surprisingly useful thing! But how can we compute it ourselves?

##Composing Composition.

When using curried binary operators like + and - we can't compose with (.) because (.) has the wrong signature:

(.) :: (b -> c) -> (a -> b) -> a -> c

as such the expression

let sqrtAdd = sqrt . (+) 

does not make any sense and wont compile. What we would need to do instead is sacrifice point-free expression with:

let sqrtAdd x y = sqrt ( x + y )

or, worse do a half-assed point-free

let sqrtAdd x = sqrt ( (+) x )

Alternatively we could create a binary composition function that gets the job done

binComp :: (c -> d) -> (a -> b -> c) -> a -> b -> d
binComp f g = \x y -> f ( g x y )
let sqrtAdd = sqrt `binComp` (+) -- works just fine

Ok, no problem. Not very interesting, right? Well here's an alternative way to define binComp that IS interesting

binComp :: (c -> d) -> (a -> b -> c) -> a -> b -> d
binComp = (.).(.)

Again it will work just as it did before. WOT? composing composition huh? whoa... Of course in cases where the functions are uncurried and acting on a tuple (.) is all you need so this is sort of an interesting result having only to do with how curried things work which is sort of non-obvious. But let's work thorugh it step by step to see how these two curried instances of (.) and (.) compose!

first let's rename (.) twice for its arguments to disambiguate the situation let f = (.) and let g = (.) such that we have

binComp = f . g

Now let's think of f and g as curried since (.) takes 2 single argument functions giving f and g different distinct types:

f :: (b -> c) -> (a -> b) -> a -> c

curried that means:
f takes a (b -> c) and returns a (a -> b) -> a -> c
input: (b -> c)
outpt: (a -> b) -> a -> c

similarly, g :: (b1 -> c1) -> (a1 -> b1) -> a1 -> c1 curried means:
g takes a (b1 -> c1) and returns a (a1 -> b1) -> a1 -> c1
input: (b1 -> c1)
outpt: (a1 -> b1) -> a1 -> c1

Now we can start matching up our types according to the type signature of (.)
Let's use capitals to differentiate

(.) :: (B -> C) -> (A -> B) -> A -> C

We'll "cheat" and just think of (.) uncurried as
(.) takes a (B -> C) and then a (A -> B) and returns a A -> C
inputs: (B -> C), (A -> B)
output: A -> C

A is g's input: (b1 -> c1)
B is f's input: (b -> c)
but also is g's outpt: (a1 -> b1) -> a1 -> c1
so therefore we can eliminate b and c for their more informative type alias:
b is (a1 -> b1), and
c is (a1 -> c1)

C is f's outpt: (a -> b) -> a -> c

but we must replace b and c with (a1 -> b1) and (a1 -> c1)

so C is actually (a -> a1 -> b1) -> a -> a1 -> c1

and so A -> C, the result of the composition and thus the type signature for binComp is
(b1 -> c1) -> (a -> a1 -> b1) -> a -> a1 -> c1

now let's remap:
b1 to c
c1 to d
a1 to b

and we've got the proper type for binComp

(c -> d) -> (a -> b -> c) -> a -> b -> d

Whoa...

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