Как и почему 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