функции как аппликативные функторы (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]