##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...