Мы применили первую функцию из списка ко всем аргументам, потом вторую функцию, третью и объединили
все результаты в список.
Теперь мы можем закончить наше определение для lift2:
lift2 :: Kleisli m => (a -> b -> c) -> m a -> m b -> m c
lift2 f a b = f’ $$ b
where f’ = lift1 f a
Мы можем записать это определение более кратко:
lift2 :: Kleisli m => (a -> b -> c) -> m a -> m b -> m c
lift2 f a b = lift1 f a $$ b
Теперь давайте добавим это определение в модуль Kleisli и посмотрим в интерпретаторе как работает
эта функция:
*Kleisli> :reload
[2 of 2] Compiling Kleisli
( Kleisli. hs, interpreted )
Ok, modules loaded: Kleisli, Nat.
*Kleisli> lift2 (+) (Just 2) (Just 2)
Just 4
*Kleisli> lift2 (+) (Just 2) Nothing
Nothing
Как на счёт функций трёх и более аргументов? У нас уже есть функции lift1 и lift2 определим функцию
lift3:
lift3 :: Kleisli m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d lift3 f a b c = …
Первые два аргумента мы можем применить с помощью функции lift2. Посмотрим на тип получивше-
гося выражения:
lift2
:: Kleisli m => (a’ -> b’ -> c’) -> m a’ -> m b’ -> m c’
f
:: a -> b -> c -> d
lift2 f a b :: m (c -> d)
— a’ == a, b’ == b, c’ == c -> d
У нас опять появился тип m (c -> d) и к нему нам нужно применить значение m c, чтобы получить m d.
Этим как раз и занимается функция $$. Итак итоговое определение примет вид:
lift3 :: Kleisli m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d lift3 f a b c = lift2 f a b $$ c
Так мы можем определить любую функцию liftN через функции liftN—1 и $$.
Несколько полезных функций
Теперь мы умеем применять к специальным значениям произвольные обычные функции. Определим ещё
несколько полезных функций. Первая функция принимает список специальных значений и собирает их в
специальный список:
Применение функций | 95
import Prelude hiding (id, (>> ), pred, sequence)
sequence :: Kleisli m => [m a] -> m [a]
sequence = foldr (lift2 (:)) (idK [])
Мы “спрячем” из Prelude одноимённую функцию sequence. Посмотрим на примеры:
*Kleisli> sequence [Just 1, Just 2, Just 3]
Just [1,2,3]
*Kleisli> sequence [Just 1, Nothing, Just 3]
Nothing
Во второй команде вся функция вернула Nothing потому что при объединении списка встретилось зна-
чение Nothing, это равносильно тому, что мы объединяем в один список, значения полученные из функций,
которые могут не вычислить результат. Поскольку значение одного из элементов не определено, весь список
не определён.
Посмотрим как работает эта функция на списках:
*Kleisli> sequence [[1,2,3], [11,22]]
[[1,11],[1,22],[2,11],[2,22],[3,11],[3,22]]
Она составляет список всех комбинаций элементов из всех подсписков.
С помощью этой функции мы можем определить функцию mapK. Эта функция является аналогом обычной
функции map, но она применяет специальную функцию к списку значений.
mapK :: Kleisli m => (a -> m b) -> [a] -> m [b]
mapK f = sequence . map f
6.4 Функторы и монады
В этой главе мы выписали вручную все определения для класса Kleisli. Мы сделали это потому, что на
самом деле в арсенале стандартных средств Haskell такого класса нет. Класс Kleisli строит замкнутый мир
специальных функций a -> m b. Его цель построить язык в языке и сделать программирование со специ-
альными функциями таким же удобным как и с обычными функциями. Мы пользовались классом Kleisli
исключительно в целях облегчения понимания этого мира. Впрочем никто не мешает нам определить этот
класс и пользоваться им в наших программах.
А пока посмотрим, что есть в Haskell и как это соотносится с тем, что мы уже увидели. С помощью класса
Kleisli
мы научились делать три различных операции применения:
Применение:
• обычных функций одного аргумента к специальным значениям (функция +$).
• обычных функций произвольного числа аргументов к специальным значениям (функции +$ и $$)
• специальных функций к специальным значениям (функция *$).
В Haskell для решения этих задач предназначены три отдельных класса. Это функторы, аппликативные
функторы и монады.
Функторы
Посмотрим на определение класса Functor:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Тип метода fmap совпадает с типом для функции +$:
(+$) :: Kleisli m => (a -> b) -> m a -> m b
Нам только нужно заменить m на f и зависимость от Kleisli на зависимость от Functor:
Итак в Haskell у нас есть базовая операция fmap применения обычной функции к значению из мира спе-
циальных функций. В модуле Control.Applicative определён инфиксный синоним для этой функции.
96 | Глава 6: Функторы и монады: теория
Аппликативные функторы
Посмотрим на определение класса Applicative:
class Functor f => Applicative f where
pure
:: a -> f a
( )
:: f (a -> b) -> f a -> f b
Если присмотреться к типам методов этого класса, то мы заметим, что это наши старые знакомые idK и
$$. Если для данного типа f определён экземпляр класса Applicative, то из контекста следует, что для него
также определён и экземпляр класса Functor.
Значит у нас есть функции fmap (или lift1) и (или $$). С их помощью мы можем составить функции