Haskell поможет с. и $ - PullRequest
       10

Haskell поможет с. и $

6 голосов
/ 03 апреля 2011

В качестве примера возьмем следующее

type Row a = [a]
type Table a = [Row a]

mapTable :: (a -> b) -> Table a -> Table b
mapTable = map . map

notTable :: Table Bool -> Table Bool
notTable = map . map $ (not)

Почему, если я удаляю $ из notTable, он перестает работать?

Я объяснил это себе несколько раз,но это никогда не слипается, и мне нужно время, чтобы понять, что происходит.Я знаю, что $ в основном гарантирует, что каждая сторона $ оценивается отдельно, потому что $ имеет наименьший приоритет, но почему это прерывается, если я вытаскиваю $?

Спасибо

Ответы [ 3 ]

10 голосов
/ 03 апреля 2011

Вы правы относительно приоритета: . - это инфикср 9 (9 - наивысший), а $ - это инфикср 0 (0 - наименьший).См. Отчет по Haskell для таблицы исправления оператора.

Однако приложение функции имеет более высокий приоритет, чем любой оператор, даже ..Следовательно:

map . map $ (not)

становится:

(map . map) $ (not)

, тогда как

map . map (not)

становится:

map . (map not)
3 голосов
/ 03 апреля 2011

В ответ на вопрос, который вы задали в качестве комментария к ответу Джои Адамса: «Почему 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].

2 голосов
/ 03 апреля 2011

Приложение функции связывается очень плотно, поэтому без $ оно анализируется как map . (map not), а не (map . map) not, что является необходимой вам интерпретацией.

...