Так что это вроде 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]