Подтвердить что ты не робот

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

Я понимаю, что это беспорядочно, так что спасибо за то, что он со мной.

4b9b3361

Ответ 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]