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