(.)
takes two functions that take one value and return a value:
(.) :: (b -> c) -> (a -> b) -> a -> c
Let’s ignore types for a moment and just use lambda calculus.
Desugar infix notation:
(.) (.) (.)
Eta-expand:
(\ a b -> (.) a b) (\ c d -> (.) c d) (\ e f -> (.) e f)
Inline the definition of (.)
:
(\ a b x -> a (b x)) (\ c d y -> c (d y)) (\ e f z -> e (f z))
Substitute a
:
(\ b x -> (\ c d y -> c (d y)) (b x)) (\ e f z -> e (f z))
Substitute b
:
(\ x -> (\ c d y -> c (d y)) ((\ e f z -> e (f z)) x))
Substitute e
:
(\ x -> (\ c d y -> c (d y)) (\ f z -> x (f z)))
Substitute c
:
(\ x -> (\ d y -> (\ f z -> x (f z)) (d y)))
Substitute f
:
(\ x -> (\ d y -> (\ z -> x (d y z))))
Resugar lambda notation:
\ x d y z -> x (d y z)
And if you ask GHCi, you’ll find that this has the expected type. Why? Because the function arrow is right-associative to support currying: the type (b -> c) -> (a -> b) -> a -> c
really means (b -> c) -> ((a -> b) -> (a -> c))
. At the same time, the type variable b
can stand for any type, including a function type. See the connection?