функции как аппликативные функторы (Haskell / LYAH)
Глава 11 узнать вы на Haskell вводит следующее определение:
instance Applicative ((->) r) where
pure x = (_ -> x)
f <*> g = x -> f x (g x)
здесь автор занимается каким-то нехарактерным для него маханием рукой ("реализация экземпляра для немного загадочна, поэтому лучше всего просто [показать ее в действии, не объясняя этого]"). Надеюсь, кто-нибудь поможет мне разобраться.
согласно прикладному определению класса,(<*>) :: f (a -> b) -> f a -> f b
в данном случае, подставляя ((->)r) на f: r->(a->b)->(r->a)->(r->b)
Итак, первый вопрос: как мне перейти от этого типа к f <*> g = x -> f x (g x)?
но даже если я принимаю эту последнюю формулу как должное, мне трудно заставить ее согласиться с примерами, которые я даю GHCi. Например:
Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4
17
это выражение вместо этого появляется в соответствии с f <*> g = x -> f (g x) (обратите внимание, что в этой версии x не появляется после f.
Я понимаю, что это грязно, так что спасибо за терпение со мной.
3 ответов
прежде всего, помните, как fmap определен прозрачна:
fmap f x = pure f <*> x
это означает, что ваш пример совпадает с (fmap (+ 5) (* 3)) 4. The fmap функция для функций - это просто композиция, поэтому ваше точное выражение такое же, как ((+ 5) . (* 3)) 4.
теперь давайте подумаем о том, почему экземпляр написан так, как он есть. Что?!--6--> does по существу применяет функцию в функторе к значению в функторе. Специализируясь на (->) r, это означает, что он применяет функция, возвращаемая функцией из r к значению, возвращаемому функцией из r. Функция, которая возвращает функцию, является функцией двух аргументов. Поэтому реальный вопрос заключается в следующем: как бы вы применили функцию из двух аргументов (r и a, возвратив b) стоимостью a возвращается функцией из r?
первое, что нужно отметить, это то, что вы должны вернуть значение типа (->) r что означает результат также должен быть функцией от r. Для справки, вот <*> функция:
f <*> g = \x -> f x (g x)
так как мы хотим вернуть функцию, принимающую значение типа r, x :: r. Функция возвращаемся должен иметь тип r -> b. Как мы можем получить значение типа b? Ну, у нас есть функция f :: r -> a -> b. С r будет аргументом функции результата, мы получаем это бесплатно. Итак, теперь у нас есть функция от a -> b. Итак, пока у нас есть некоторое значение типа a, мы можем получить значение типа b. Но как мы получаем значение типа a? Ну, у нас есть другая функция g :: r -> a. Таким образом, мы можем взять наше значение типа r (параметр x) и использовать его, чтобы получить значение типа a.
Итак, конечная идея проста: мы используем параметр, чтобы сначала получить значение типа a вставить в g. Параметр имеет тип r, g типа r -> a, так что у нас есть a. Затем мы подключаем как параметр, так и новый значение в f. Нам нужны оба, потому что f тип r -> a -> b. Как только мы подключим оба r и a в, у нас есть b1. Поскольку параметр находится в лямбде, результат имеет тип r -> b, чего мы и хотим.
(+) <$> (+3) <*> (*100) $ 5
это то же, что:
pure (+) <*> (+3) <*> (*100) $ 5
ключ здесь pure до (+), который имеет эффект бокса (+) как Аппликатив. Если вы посмотрите на то, как pure определено, вы можете видеть, что для его распаковки вам нужно предоставить дополнительный аргумент, который может быть чем угодно. Применяющий <*> to (+) <$> (+3), мы получим
\x -> (pure (+)) x ((+3) x)
уведомления (pure (+)) x, мы применяем x to pure распаковывать (+). Так что теперь у нас есть
\x -> (+) ((+3) x)
добавлять (*100) и (+) <$> (+3) <*> (*100) и применить <*> опять же, мы получаем
\x -> (+) ((+3) x) ((*100) x)
Итак, в заключение -x после f не является первым аргументом нашего двоичного оператора, он используется для распаковки оператора внутри pure.
" в данном случае, подставляя
((->)r)наf:r->(a->b)->(r->a)->(r->b)"
почему, это неправильно. Это на самом деле (r->(a->b)) -> (r->a) -> (r->b), а равно (r->a->b) -> (r->a) -> r -> b. То есть, мы сопоставляем инфикс и функцию, которая возвращает правый аргумент инфикса, с функцией, которая принимает только инфикс и возвращает его результат. Например,
Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]