As dave4420 mentions,
(.) :: (b -> c) -> (a -> b) -> a -> c
So what is the type of (.) (.)
? dave4420 skips that part, so here it is: (.)
accepts a value of type b -> c
as its first argument, so
(.) :: ( b -> c ) -> (a -> b) -> a -> c
(.) :: (d -> e) -> ((f -> d) -> f -> e)
so we have b ~ d->e
and c ~ (f -> d) -> f -> e
, and the resulting type of (.)(.)
is (a -> b) -> a -> c
. Substituting, we get
(a -> d -> e) -> a -> (f -> d) -> f -> e
Renaming, we get (a -> b -> c) -> a -> (d -> b) -> d -> c
. This is a function f
that expects a binary function g
, a value x
, a unary function h
and another value y
:
f g x h y = g x (h y)
That's the only way this type can be realized: g x :: b -> c
, h y :: b
and so g x (h y) :: c
, as needed.
Of course in Haskell, a "unary" function is such that expects one or more arguments; similarly a "binary" function is such that expects two or more arguments. But not less than two (so using e.g. succ
is out of the question).
We can also tackle this by writing equations, combinators-style1. Equational reasoning is easy:
(.) (.) x y z w q =
((.) . x) y z w q =
(.) (x y) z w q =
(x y . z) w q =
x y (z w) q
We just throw as much variables as needed into the mix and then apply the definition back and forth. q
here was an extra, so we can throw it away and get the final definition,
_BB x y z w = x y (z w)
(coincidentally, (.)
is known as B-combinator).
1 a b c = (\x -> ... body ...)
is equivalent to a b c x = ... body ...
, and vice versa, provided that x
does not appear among {a,b,c}
.