В ответ на вопрос, который вы задали в качестве комментария к ответу Джои Адамса: «Почему map . map $ not = (map . map) $ not
работает, а map . map not = map . (map not)
нет?»
Давайте сначала рассмотрим, что делает map . map
.Прежде всего, map
принимает функцию f :: a -> b
и список с типом [a]
, давая список с типом [b]
, где f
применяется к каждому элементу исходного списка.Тип map
является (a -> b) -> [a] -> [b]
.Напомним, что в Haskell это означает, что map
действительно является функцией, которая принимает функцию a -> b
и возвращает функцию, принимающую [a]
и выдающую [b]
.Нам часто нравится думать, что map
является функцией двух переменных, но различие будет важно позже.
Теперь давайте рассмотрим, что делает оператор композиции (.)
.Напомним, что он определен как
(.) :: (b1 -> c1) -> (a1 -> b1) -> (a1 -> c1)
f . g = \ x -> f (g x)
, то есть он принимает две функции f
и g
(с подходящими доменами / входами и целями / выходами) и дает вам новую функцию, определенную первым применениемg
и затем применение f
к тому, что g
выплевывает.Я назвал переменные типа a1
, b1
и c1
, чтобы избежать путаницы позже.
ОК, теперь мы в состоянии выяснить, что такое map . map
.Для ясности запишем две (идентичные) карты как
mapleft :: (c -> d) -> [c] -> [d]
mapleft = map
mapright :: (a -> b) -> [a] -> [b]
mapright = map
. Теперь способ, которым "функции двух переменных" кодируются в Haskell, становится важным.Поскольку функции в Haskell действительно имеют только один ввод, мы должны быть осторожны, как обсуждалось выше.Таким образом, домен / вход mapright
действительно имеет тип a -> b
, а вывод действительно [a] -> [b]
.Возвращаясь к сигнатуре (.)
, это означает, что мы исправили тип правого операнда a1 -> b1
выше, чтобы быть (a -> b) -> ([a] -> [b])
.Таким образом, a1 = a -> b
и b1 = [a] -> [b]
.
Поступая аналогичным образом для левого операнда, мы видим, что [a] -> [b] = b1 = c -> d
, поэтому c = [a]
и d = [b]
.То же самое рассуждение дает c1 = [c] -> [d] = [[a]] -> [[b]]
.
И все готово, теперь мы можем прочитать тип leftmap . rightmap = map . map
: Это
a1 -> c1 = (a -> b) -> [[a]] -> [[b]]
.Это подтверждается GHCi:
Prelude> :t (map . map)
(map . map) :: (a -> b) -> [[a]] -> [[b]]
Теперь станет ясно, почему две функции, о которых вы говорите, разные.Ясно, что (map . map) not
имеет тип [[Bool]] -> [[Bool]]
, который именно то, что вы хотите.map not
, с другой стороны, имеет тип [Bool] -> [Bool]
.Взяв вывод map not
и введя его в ( первый ) вход map
, вы даже не проверите проверку: первый вход map
должен быть функцией ,в то время как вывод map not
равен [Bool]
.