Функционирует как аппликативные функторы (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
.
Я понимаю, что это беспорядочно, так что спасибо за то, что он со мной.
Ответы
Ответ 1
Прежде всего, помните, как fmap
определяется для аппликаций:
fmap f x = pure f <*> x
Это означает, что ваш пример совпадает с (fmap (+ 5) (* 3)) 4
. Функция fmap
для функций - это просто композиция, поэтому ваше точное выражение совпадает с ((+ 5) . (* 3)) 4
.
Теперь подумайте, почему экземпляр написан так, как он есть. То, что <*>
делает, по существу, применяет функцию в функторе к значению в функторе. Специализируясь на (->) 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
, который мы хотим.
Ответ 2
Рассматривая ваш первоначальный вопрос, я думаю, что есть один тонкий, но очень важный момент, который вы, возможно, пропустили. Используя оригинальный пример от LYAH:
(+) <$> (+3) <*> (*100) $ 5
Это так же, как:
pure (+) <*> (+3) <*> (*100) $ 5
Ключевым моментом здесь является pure
перед (+)
, который имеет эффект бокса (+)
в качестве аппликатива. Если вы посмотрите на то, как определяется pure
, вы увидите, что для его распаковки вам нужно предоставить дополнительный аргумент, который может быть любым. Применяя <*>
к (+) <$> (+3)
, мы получаем
\x -> (pure (+)) x ((+3) x)
Обратите внимание, что в (pure (+)) x
мы применяем x
к pure
для unbox (+)
. Итак, теперь у нас есть
\x -> (+) ((+3) x)
Добавив (*100)
к get (+) <$> (+3) <*> (*100)
и применив <*>
снова, мы получим
\y -> (\x -> (+) ((+3) x)) y ((*100) y) {Since f <*> g = f x (g x)}
5 -> (\x -> (+) ((+3) x)) 5 ((*100) 5)
(\x -> (+) ((+3) x)) 5 (500)
5 -> (+) ((+3) 5) (500)
(+) 8 500
508
Таким образом, в заключение, x
после f
НЕ является первым аргументом для нашего бинарного оператора, он используется для UNBOX оператора внутри pure
.
Ответ 3
"В этом случае подстановка ((->)r)
для f
: r->(a->b)->(r->a)->(r->b)
"
Почему, это не так. Это фактически (r->(a->b)) -> (r->a) -> (r->b)
, и это то же самое, что и (r->a->b) -> (r->a) -> r -> b
. I.e., Мы отображаем инфикс и функцию, которая возвращает правый аргумент infix, к функции, которая принимает только инфикс LHS и возвращает его результат. Например,
Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]