I believe I understand fmap . fmap
for Functors, but on functions it\'s hurting my head for months now.
I\'ve seen that you can just apply the definitio
Your solution diverges when you introduce y
. It should be
\x f y -> ((.) ((.) x) f) y :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z)) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
Which matches the original type signature: (.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(It's easiest to do the expansion in ghci, where you can check each step with :t expression
)
Edit:
The deeper intution is this:
(.)
is simply defined as
\f g -> \x -> f (g x)
Which we can simplify to
\f g x -> f (g x)
So when you supply it two arguments, it's curried and still needs another argument to resolve.
Each time you use (.)
with 2 arguments, you create a "need" for one more argument.
(.) . (.)
is of course just (.) (.) (.)
, so let's expand it:
(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))
We can beta-reduce on f0
and g0
(but we don't have an x0
!):
\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0)
Substitute the 2nd expression for f1
...
\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1)
Now it "flips back"! (beta-reduction on f2
):
This is the interesting step - x0
is substituted for f2
-- This means that x
, which could have been data, is instead a function.
This is what (.) . (.)
provides -- the "need" for the extra argument.
\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1)
This is starting to look normal...
Let's beta-reduce a last time (on g2
):
\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))
So we're left with simply
\x0 g1 x1 x2 -> x0 ((g1 x1) x2)
, where the arguments are nicely still in order.