Когда мы начали своё путешествие на верхушку Горы Монад, мы сначала посмотрели на
Итак, каждая монада – это аппликативный функтор, а каждый аппликативный функтор – это функтор. Класс типов Applicative
имеет такое ограничение класса, ввиду которого наш тип должен иметь экземпляр класса Functor
, прежде чем мы сможем сделать для него экземпляр класса Applicative
. Класс Monad
должен иметь то же самое ограничение для класса Applicative
, поскольку каждая монада является аппликативным функтором – однако не имеет, потому что класс типов Monad
был введён в язык Haskell задолго до класса Applicative
.
Но хотя каждая монада – функтор, нам не нужно полагаться на то, что у неё есть экземпляр для класса Functor
, в силу наличия функции liftM
. Функция liftM
берёт функцию и монадическое значение и отображает монадическое значение с помощью функции. Это почти одно и то же, что и функция fmap
! Вот тип функции liftM
:
liftM :: (Monad m) => (a –> b) –> m a –> m b
Сравните с типом функции fmap
:
fmap :: (Functor f) => (a –> b) –> f a –> f b
Если экземпляры классов Functor
и Monad
для типа подчиняются законам функторов и монад, между этими двумя нет никакой разницы (и все монады, которые мы до сих пор встречали, подчиняются обоим). Это примерно как функции pure
и return
, делающие одно и то же, – только одна имеет ограничение класса Applicative
, тогда как другая имеет ограничение Monad
.
Давайте опробуем функцию liftM
:
ghci> liftM (*3) (Just 8)
Just 24
ghci> fmap (*3) (Just 8)
Just 24
ghci> runWriter $ liftM not $ Writer (True, "горох")
(False,"горох")
ghci> runWriter $ fmap not $ Writer (True, "горох")
(False,"горох")
ghci> runState (liftM (+100) pop) [1,2,3,4]
(101,[2,3,4])
ghci> runState (fmap (+100) pop) [1,2,3,4]
(101,[2,3,4])
Вы уже довольно хорошо знаете, как функция fmap
работает со значениями типа Maybe
. И функция liftM
делает то же самое. При использовании со значениями типа Writer
функция отображает первый компонент кортежа, который является результатом. Выполнение функций fmap
или liftM
с вычислением, имеющим состояние, даёт в результате другое вычисление с состоянием, но его окончательный результат изменяется добавленной функцией. Если бы мы не отобразили функцию pop
с помощью (+100)
перед тем, как выполнить её, она бы вернула (1, [2,3,4])
.
Вот как реализована функция liftM
:
liftM :: (Monad m) => (a –> b) –> m a –> m b
liftM f m = m >>= (\x –> return (f x))
Или с использованием нотации do
:
liftM :: (Monad m) => (a –> b) –> m a –> m b
liftM f m = do
x <– m
return (f x)
Мы передаём монадическое значение m
в функцию, а затем применяем функцию к его результату, прежде чем поместить его обратно в контекст по умолчанию. Ввиду монадических законов гарантируется, что функция не изменит контекст; она изменяет лишь результат, который представляет монадическое значение.
Вы видите, что функция liftM
реализована совсем не ссылаясь на класс типов Functor
. Значит, мы можем реализовать функцию fmap
(или liftM
– называйте, как пожелаете), используя лишь те блага, которые предоставляют нам монады. Благодаря этому можно заключить, что монады, по крайней мере, настолько же сильны, насколько и функторы.
Класс типов Applicative
позволяет нам применять функции между значениями с контекстами, как если бы они были обычными значениями, вот так:
ghci> (+) <$> Just 3 <*> Just 5
Just 8
ghci> (+) <$> Just 3 <*> Nothing
Nothing
Использование этого аппликативного стиля всё упрощает. Операция <$>
– это просто функция fmap
, а операция <*>
– это функция из класса типов Applicative
, которая имеет следующий тип:
(<*>) :: (Applicative f) => f (a –> b) –> f a –> f b