В то время как вы пытаетесь лучше понять Аппликативный, я рассмотрел определение < * > , которое имеет тенденцию быть определено как ap, которое, в свою очередь, определяется как:
Я не понимаю, как просто переходя в id, соответствующая часть сигнатуры типа преобразуется от (a1 -> a2 -> r) -> m a1
в m (a -> b)
. Что мне здесь не хватает?
Ответ 2
Самый верный ответ определенно правильный и работает быстро и эффективно от самих типов. Как только вы хорошо разбираетесь в Haskell (отказ от ответственности: я не знаю), тогда это гораздо более эффективный способ понять это, чем проложить определение функций.
Но поскольку мне недавно пришлось бороться именно с этой проблемой с ap
, пока я работал над The Monad Challenges, я решил поделиться мой опыт, потому что он может предоставить некоторую дополнительную интуицию.
Во-первых, так же, как спрашивает 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
.
Предположим, что мы должны были передать функциональную Monad как первую часть выше, часть ma
. Это может быть что-то вроде Just (+3)
или [(+3), (*2)]
- некоторая "функция", живущая внутри контекста Монады.
И мы снабжаем аргументированную Monad как вторую часть, часть mb
, такую как Just 5
или [6,7,8]
- некоторое "значение", живущее в контексте Monad, которое может служить аргументом для функция, живущая внутри 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
: он имитирует извлечение значение из контекста Monad по модулю любой специальной обработки, которая уникальна для этой Monad.
Итак, окончательная строка действительно станет
return $ f someFunction someArgument
Теперь отбросьте назад и запомните, что наша цель при создании ap
состоит в вызове someFunction
после someArgument
внутри контекста Monad. Итак, что бы ни производило использование 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
, возвращая результат внутри контекста Monad.
Это похоже на то, что мы вынудили 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