For posterity, here’s a tip for how you can figure this kind of thing out in the future. You can ask the type of a subexpression in ghci using a wildcard (as long as you don’t have PartialTypeSignatures enabled).
:t ((fmap :: _) . (fmap :: _)) sum Just [1, 2, 3]
This tells you that the outer fmap is used at type (a1 -> b) -> ([a] -> a1) -> [a] -> b while the inner one is used at type (a1 -> b) -> Maybe a1 -> Maybe b. It should be clear that the inner fmap is simply acting on Maybe. The outer fmap looks more complicated at first glance, but it’s an instantiation of the type of (.), which is fmap in the function reader functor.
(.) :: ( y -> z) -> ( x -> y) -> x -> z
fmap :: (a1 -> b) -> ([a] -> a1) -> [a] -> b
x ~ [a]
y ~ a1
z ~ b
The functor here is ([a] ->), spelled (->) [a] in real code because Haskell doesn’t allow operator sections at the type level.
That tells you that the outer one is (.), and from there you can inline the definitions step by step to see how this is evaluated.
(fmap . fmap) sum Just [1, 2, 3]
fmap (fmap sum) Just [1, 2, 3]
(.) (fmap sum) Just [1, 2, 3]
(fmap sum . Just) [1, 2, 3]
fmap sum (Just [1, 2, 3])
To reduce noise and avoid having to juggle so many type variables to determine which are relevant, you can fix some of the types with type signatures, here Int instead of Num a => a.
:t ((fmap :: _) . (fmap :: _)) sum Just [1 :: Int, 2, 3]
This gives (a -> b) -> ([Int] -> a) -> [Int] -> b for the outer fmap in the (->) [Int] functor, and (a -> b) -> Maybe a -> Maybe b for the inner fmap in Maybe.
And just for fun, going the other way, you can also replace the (.) with an fmap!
fmap fmap fmap sum Just [1, 2, 3]