First you do parenthesis, so you do fmap . fmap
.
Afterwards you have just function application so:
sum applied to (fmap . fmap)
Just applied to ((fmap . fmap) sum)
[1, 2, 3] applied to (((fmap . fmap) sum) Just)
You have functions and you apply functions to them and you get functions.
You can do type driven development to find out their types.
You can use holes or ask the REPL for the type of things.
f :: _
f = fmap . fmap
tells us
• Found type wildcard ‘_’
standing for ‘(a -> b) -> f0 (f1 a) -> f0 (f1 b)’
and
• Ambiguous type variable ‘f0’ arising from a use of ‘fmap’
prevents the constraint ‘(Functor f0)’ from being solved.
and
• Ambiguous type variable ‘f1’ arising from a use of ‘fmap’
prevents the constraint ‘(Functor f1)’ from being solved.
so we conclude
f :: (Functor f0, Functor f1) => (a -> b) -> f0 (f1 a) -> f0 (f1 b)
f = fmap . fmap
Next, applying sum, and repeating the same finding out process:
g :: (Foldable t0, Functor f0, Functor f1) => f0 (f1 (t0 Integer)) -> f0 (f1 Integer)
g = f sum
Note, this is actually:
g :: (Foldable t0, Functor f0, Functor f1, Num a) => f0 (f1 (t0 a)) -> f0 (f1 a)
g = f sum
-- recall sum
-- sum :: (Foldable t, Num a) => t a -> a
And again:
h :: (Foldable t0, Num a) => t0 a -> Maybe a
h = g Just
And again:
i :: Maybe Integer
i = h [1, 2, 3]
You can do the reverse to verify:
j = let f2 = fmap . fmap
g2 = f2 sum
h2 = g2 Just :: _
in h2 [1, 2, 3]
reveals
h2 :: [Integer] -> Maybe Integer
and
j = let f2 = fmap . fmap
g2 = f2 sum :: _
h2 = g2 Just :: [Integer] -> Maybe Integer
in h2 [1, 2, 3]
reveals
g2 :: ([Integer] -> Maybe [Integer]) -> [Integer] -> Maybe Integer
and
j = let f2 = fmap . fmap :: _
g2 = f2 sum :: ([Integer] -> Maybe [Integer]) -> [Integer] -> Maybe Integer
h2 = g2 Just :: [Integer] -> Maybe Integer
in h2 [1, 2, 3]
reveals
f2 :: (a1 -> b1) -> ([Integer] -> Maybe a1) -> [Integer] -> Maybe b1
and now you can match them, for example:
g :: (Foldable t0, Functor f0, Functor f1, Num a) => f0 (f1 (t0 a)) -> f0 (f1 a)
g2 :: ([Integer] -> Maybe [Integer]) -> [Integer] -> Maybe Integer
remembering that functions always take one value and return one value, I add redundant parens
g2 :: ([Integer] -> Maybe [Integer]) -> ([Integer] -> Maybe Integer)
remembering that a -> b
is (->) a b
g2 :: ((->) [Integer] Maybe [Integer]) -> ((->) [Integer] Maybe Integer)
a bit of redundant parens
g2 :: (((->) [Integer]) Maybe [Integer]) -> (((->) [Integer]) Maybe Integer)
and we can see it checks out
[] satisfies Foldable t0
(-> [Integer]) satisfies Functor f0
Maybe satisfies Functor f1
Integer satisfies Num a
And why does (fmap . fmap) look like that? Function application, too:
fmap :: Functor f => (a -> b) -> f a -> f b
(.) :: (b -> c) -> (a -> b) -> a -> c
-- fmap :: (a -> b) -> f a -> f b ~ (a -> b) -> (f a -> f b)
-- in first argument of (.) we have: b ~ (a1 -> b1); c ~ (f1 a1 -> f1 b1)
-- applying we have: (.) fmap :: Functor f1 => (a -> (a1 -> b1)) -> a -> (f1 a1 -> f1 b1)
-- in first argument of (.) fmap we have a ~ (a2 -> b2); (a1 -> b1) ~ (f2 a2 -> f2 b2)
-- applying we have: (.) fmap fmap :: Functor f1, f2 => (a2 -> b2) -> (f1 (f2 a2) -> f1 (f2 b2))
-- cleaning up we have: (.) fmap fmap :: Functor f0, f1 => (a -> b) -> f0 (f1 a) -> f0 (f1 b)
see
f :: _
f = (.) fmap
• Found type wildcard ‘_’
standing for ‘(a -> a1 -> b) -> a -> f0 a1 -> f0 b’
checks out!