So, you can see fmap as a unary funcrion that takes an argument of type (a->b) and returns a result of type (f a -> f b).
For example, if you define
identity x = x
then
fmap identity :: Functor f => f b -> f b
(here a = b because identity :: p -> p )
Now, the type of (.) is;
(.) :: (b -> c) -> (a -> b) -> (a -> c)
if you write
fmap . fmap, in general (a priori) the type of the fmaps could be
FIRSTFMAP fmap :: Functor f1 => (d -> e) -> f1 d -> f1 e
SECONDFMAP fmap :: Functor f2 => (g -> h) -> f2 g -> f2 h
let's see if there must be some bonds between types d,e,g,h, due to the type of (.)
(b -> c) = FIRSTFMAP (the first argument of (.) )
(a -> b) = SECONDFMAP
(the second argument of (.) )
so, we have (I'll not write "Functor f1" or "Functor f2", we know f1 and f2 are functors)
b = (d -> e)
c = (f1 d -> f1 e)
a = (g -> h)
b = (f2 g -> f2 h)
now, it must be b=b, so the type (d -> e) must be the same type of (f2 g -> f2 h)
So
d = f2 g
e = f2 h
The result of fmap . fmap, will be of type (a -> c) that is
(you only need to add that f1, f2 are Functors, and the names of f and g can of course be replaced with a and b)