Как и почему ap определяется как идентификатор liftM2 в Haskell

Пытаясь лучше понять Applicative, я рассмотрел определение <*>, которое обычно определяется как ap, которое, в свою очередь, определяется как:

ap                :: (Monad m) => m (a -> b) -> m a -> m b
ap                =  liftM2 id

Посмотрим на подписи типа для liftM2 и id, а именно:

liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
id                      :: a -> a

Я не понимаю, как при передаче идентификатора соответствующая часть сигнатуры типа, по-видимому, трансформируется из (a1 -> a2 -> r) -> m a1 в m (a -> b), Что мне здесь не хватает?

2 ответа

Решение

Переменная типа a от id может быть создан в любом типе, и в этом случае этот тип a -> b,

Итак, мы создаем id в (a -> b) -> (a -> b), Теперь переменная типа a1 от liftM2 создается в (a -> b), a2 создается в a, а также r создается в b,

Собираем все вместе, liftM2 создается в ((a -> b) -> (a -> b)) -> m (a -> b) -> m a -> m b, а также liftM2 id :: m (a -> b) -> m a -> m b,

Верхний ответ определенно правильный и работает быстро и эффективно только от одних типов. Если вы хорошо разбираетесь в Haskell (отказ от ответственности: нет), то это гораздо более эффективный способ понять это, чем пробежаться по определениям функций.

Но так как мне недавно пришлось бороться именно с этим вопросом с ap Пока я работал над "Задачами Монады", я решил поделиться своим опытом, потому что он может дать некоторую дополнительную интуицию.

Во-первых, как и в Monad Challenges, я буду использовать имя bind ссылаться на основной оператор Monad >>=, Я думаю, что это очень помогает.

Если мы определим нашу собственную версию liftM2 мы можем это сделать:

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
liftM2 f ma mb = 
    ma `bind` \a -> 
    mb `bind` \b -> 
    return $ f a b

Мы хотим создать ap используя это, чтобы помочь нам. Давайте оставим функцию f один на мгновение и просто подумать о том, как это может работать как ap при условии, что мы выбрали правильную функцию для f,

Предположим, что мы должны передать функциональную монаду в качестве первой части выше, ma часть. Это может быть что-то вроде Just (+3) или же [(+3), (*2)] - некоторая "функция", живущая в контексте монады.

И мы поставляем аргументированную Монаду в качестве второй части, mb часть, такая как Just 5 или же [6,7,8] - некоторая "ценность", живущая в контексте монады, которая может служить аргументом для функции, живущей внутри ma,

Тогда мы бы

liftM2 f (m someFunction) (m someArgument) = 
    (m someFunction) `bind` \a ->
    (m someArgument) `bind` \b ->
    return $ (f a b)

и внутри лямбда-функции следующие bind, мы знаем это a будет someFunction а также b будет someArgument - ибо это то, что bind has: он имитирует извлечение значения из контекста Monad по модулю любой специальной обработки, уникальной для этой Monad.

Так что последняя строка действительно становится

return $ f someFunction someArgument

Теперь давайте вернемся назад и вспомним, что наша цель в создании ap это позвонить someFunction на someArgument внутри контекста монады. Так что независимо от нашего использования return дает, это должно быть результатом применения функции someFunction someArgument,

Итак, как мы можем сделать два выражения равными

f someFunction someArgument ==? someFunction someArgument

Хорошо, если мы позволим x = (someFunction someArgument) тогда мы ищем функцию f такой, что

f x = x

и поэтому мы знаем, что f должно быть id,

Возвращаясь к началу, это означает, что мы ищем liftM2 id,

В принципе liftM2 id ma mb говорит, что я собираюсь сделать m (id a b) так что если a это функция, которая может работать на b, затем id будет "просто оставить их в покое" и позволить a делать свое дело b, возвращая результат внутри контекста монады.

Как будто мы заставили liftM2 иметь предвзятость.

И для того, чтобы это сработало, a должен иметь тип функции, который идет от "TypeOfb" до "SomeReturnType", или TypeOfb -> SomeReturnType, так как b является aОжидаемый аргумент. И конечно b должен иметь TypeOfb,

Если вы позволите мне одно злоупотребление обозначениями, то произвольно давайте просто использовать символ "a" для обозначения "TypeOfb" и символ "b" для обозначения "SomeReturnType":

`b` --> "a" is its type
`a` --> "a -> b" is its type

Тогда тип подписи для ap было бы

ap :: Monad m => m (TypeOfB -> SomeReturnType) -> m TypeOfB -> m SomeReturnType
==>
ap :: Monad m => m (a -> b) -> m a -> m b  
Другие вопросы по тегам