Так что это вроде fmap
f a
, а затем вновь собрать контекст. Поскольку все функции в языке Haskell по умолчанию каррированы, мы можем использовать сочетание из операций <$>
и <*>
между аппликативными значениями, чтобы применять функции, принимающие несколько параметров.Однако, оказывается, как и функция fmap
<*>
тоже может быть реализована, используя лишь то, что даёт нам класс типов Monad
. Функция ap
, по существу, – это <*>,
только с ограничением Monad
, а не Applicative
. Вот её определение:ap :: (Monad m) => m (a –> b) –> m a –> m b
ap mf m = do
f <– mf
x <– m
return (fx)
Функция ap
f
, затем берём значение и называем его x
, и, в конце концов, применяем функцию к значению и представляем это в качестве результата. Вот быстрая демонстрация:ghci> Just (+3) <*> Just 4
Just 7
ghci> Just (+3) `ap` Just 4
Just 7
ghci> [(+1),(+2),(+3)] <*> [10,11]
[11,12,12,13,13,14]
ghci> [(+1),(+2),(+3)] `ap` [10,11]
[11,12,12,13,13,14]
Теперь нам видно, что монады настолько же сильны, насколько и аппликативные функторы, потому что мы можем использовать методы класса Monad
Applicative
. На самом деле, когда обнаруживается, что определённый тип является монадой, зачастую сначала записывают экземпляр класса Monad
, а затем создают экземпляр класса Applicative
, просто говоря, что функция pure
– это return
, а операция <*>
– это ap
. Аналогичным образом, если у вас уже есть экземпляр класса Monad
для чего-либо, вы можете сделать для него экземпляр класса Functor
, просто говоря, что функция fmap
– это liftM
.Функция liftA2
liftA2 :: (Applicative f) => (a –> b –> c) –> f a –> f b –> f c
liftA2 f x y = f <$> x <*> y
Функция liftM2
Monad
. Есть также функции liftM3
, liftM4
и liftM5
.Вы увидели, что монады не менее сильны, чем функторы и аппликативные функторы – и, хотя все монады, по сути, являются функторами и аппликативными функторами, у них необязательно имеются экземпляры классов Functor
Applicative
. Мы изучили монадические эквиваленты функций, которые используются функторами и аппликативными функторами.Функция join
Есть кое-какая пища для размышления: если результат монадического значения – ещё одно монадическое значение (одно монадическое значение вложено в другое), можете ли вы «разгладить» их до одного лишь обычного монадического значения? Например, если у нас есть Just (Just 9)
Just 9
? Оказывается, что любое вложенное монадическое значение может быть разглажено, причём на самом деле это свойство уникально для монад. Для этого у нас есть функция join
. Её тип таков:join :: (Monad m) => m (m a) –> m a
Значит, функция join
Maybe
:ghci> join (Just (Just 9))
Just 9
ghci> join (Just Nothing)
Nothing
ghci> join Nothing
Nothing
В первой строке – успешное вычисление как результат успешного вычисления, поэтому они оба просто соединены в одно большое успешное вычисление. Во второй строке значение Nothing
Just
. Всякий раз, когда мы раньше имели дело со значениями Maybe
и хотели объединить несколько этих значений – будь то с использованием операций <*>
или >>=
– все они должны были быть значениями конструктора Just
, чтобы результатом стало значение Just
. Если на пути возникала хоть одна неудача, то и результатом являлась неудача; нечто аналогичное происходит и здесь. В третьей строке мы пытаемся разгладить то, что возникло вследствие неудачи, поэтому результат – также неудача.Разглаживание списков осуществляется довольно интуитивно:
ghci> join [[1,2,3],[4,5,6]]
[1,2,3,4,5,6]