Для потомков, вот совет, как вы можете выяснить такие вещи в будущем. Вы можете задать тип подвыражения в ghci, используя подстановочный знак (если у вас не включен PartialTypeSignatures
).
:t ((fmap :: _) . (fmap :: _)) sum Just [1, 2, 3]
Это говорит о том, что внешний тип fmap
используется в типе (a1 -> b) -> ([a] -> a1) -> [a] -> b
, а внутренний используется с типом (a1 -> b) -> Maybe a1 -> Maybe b
. Должно быть ясно, что внутренний fmap
просто действует на Maybe
. Внешний fmap
на первый взгляд выглядит более сложным, но это экземпляр типа (.)
, который равен fmap
в функторе чтения функций.
(.) :: ( y -> z) -> ( x -> y) -> x -> z
fmap :: (a1 -> b) -> ([a] -> a1) -> [a] -> b
x ~ [a]
y ~ a1
z ~ b
Функтор здесь ([a] ->)
, пишется (->) [a]
в реальном коде, потому что Haskell не позволяет разделы оператора на уровне типа.
Это говорит о том, что внешний является (.)
, и оттуда вы можете встроить определения шаг за шагом, чтобы увидеть, как это оценивается.
(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])
Чтобы уменьшить шум и избежать необходимости манипулировать таким количеством переменных типа, чтобы определить, какие из них актуальны, вы можете исправить некоторые типы с помощью сигнатур типов, здесь Int
вместо Num a => a
.
:t ((fmap :: _) . (fmap :: _)) sum Just [1 :: Int, 2, 3]
Это дает (a -> b) -> ([Int] -> a) -> [Int] -> b
для внешнего fmap
в функторе (->) [Int]
и (a -> b) -> Maybe a -> Maybe b
для внутреннего fmap
в Maybe
.
И ради развлечения, вы можете заменить (.)
на fmap
!
fmap fmap fmap sum Just [1, 2, 3]