Упражнения | 85
Глава 6
Функторы и монады: теория
Мы научились комбинировать функции наиболее общего типа a -> b. В этой главе мы посмотрим на
специальные функции и способы их комбинирования. Cпециальными функциями мы будем называть такие
функции, результат которых имеет некоторую известную нам структуру. Среди них функции, которые могут
вычислить значение или упасть, или функции, которые возвращают сразу несколько вариантов значений.
Для составления таких функций из простейших в Haskell предусмотрено несколько классов типов. Это функ-
торы и монады. Их мы и рассмотрим в этой главе.
6.1 Композиция функций
Центральной функцией этой главы будет функция композиции. Вспомним её определение для функций
общего типа:
(. ) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = x -> f (g x)
Композиция двух функций f и g это такая функция, в которой мы сначала применяем g, а затем f. Для того
чтобы тип функции стал более наглядным, мы определим эту функцию немного по-другому. Мы поменяем
аргументы местами.
(>> ) :: (a -> b) -> (b -> c) -> (a -> c)
f >> g = x -> g (f x)
Мы будем изображать функции кружками, а значения – стрелками (рис. 6.1). Значения словно текут от
узла к узлу по стрелкам. Поскольку тип стрелки выходящей из f совпадает с типом стрелки входящей в g мы
можем соединить их и получить составную функцию (f>> g).
a
f
b
b
g
c
b
a
g
f
c
a
f>>g
c
Рис. 6.1: Композиция функций
86 | Глава 6: Функторы и монады: теория
Класс Category
С помощью операции композиции можно обобщить понятие функции. Для этого существует класс
Category:
class Category cat where
id
:: cat a a
(>> ) :: cat a b -> cat b c -> cat a c
Функция cat это тип с двумя параметрами, в котором выделено специальное значение id, которое остав-
ляет аргумент без изменений. Также мы можем составлять из простых функций сложные с помощью компо-
зиции, если функции совпадают по типу. Здесь мы для наглядности также заменили метод (. ) на (>> ), но
суть остаётся прежней. Для любого экземпляра класса должны выполняться свойства:
f
>> id
== f
id >> f
== f
f >> (g >> h) == (f >> g) >> h
Первые два свойства говорят о том, что id является нейтральным элементом для (>> ) слева и справа.
Третье свойство говорит о том, что нам не важно в каком порядке проводить композицию. Можно проверить,
что эти правила выполнены для функций.
Специальные функции
Все специальные функции, которые мы рассмотрим в этой главе будут иметь один и тот же тип:
a -> m b
Смотрите вместо произвольного типа b функция возвращает m b. Единственное, что будет меняться от
раздела к разделу это тип m. Добавив этот тип к результату, мы сузили область значений функции. Простым
примером таких функций могут быть функции, которые возвращают списки:
a -> [b]
Если раньше наши функции могли возвращать произвольное значение b, то теперь мы знаем, что все
результирующие значения таких функций будут списками.
При этом для каждого такого m мы попытаемся построить свой замкнутый мир специальных функций a
-> m b. Он будет жить внутри вселенной всех произвольных функций типа a -> b. В этом нам поможет
специальный класс типов, который называется категорией Клейсли (эта конструкция носит имя математика
Хенрика Клейсли).
class Kleisli m where
idK
:: a -> m a
(*> ) :: (a -> m b) -> (b -> m c) -> (a -> m c)
Этот класс является классом Category в мире наших специальных функций. Если мы сотрём все буквы m,
то мы получим обычные типы для тождества и композиции. В этом мире должны выполняться те же правила:
f
*> idK
== f
idK *> f
== f
f *> (g *> h) == (f *> g) *> h
Взаимодействие с внешним миром
С помощью класса Kleisli мы можем составлять из одних специальных функций другие. Но как мы
сможем комбинировать специальные функции с обычными?
Поскольку слева у нашей специальной функции обычный общий тип, то с этой стороны мы можем вос-
пользоваться обычной функцией композиции >> . Но как быть при композиции справа? Нам нужна функция
типа:
(a -> m b) -> (b -> c) -> (a -> m c)
Оказывается мы можем составить её из методов класса Kleisli. Мы назовём эту функцию композиции
(+> ).
(+> ) :: Kleisli m => (a -> m b) -> (b -> c) -> (a -> m c)
f +> g = f *> (g >> idK)
С помощью метода idK мы можем погрузить в мир специальных функций любую обычную функцию.
Композиция функций | 87
Три композиции
У нас появилось много композиций целых три:
аргументы
|
результат
обычная
>>
обычная
==
обычная
специальная
+>
обычная
==
специальная
специальная
*>
специальная
==
специальная
При этом важно понимать, что по смыслу это три одинаковые функции. Они обозначают операцию по-
следовательного применения функций. Разные значки отражают разные типы функций аргументов.
Обобщённая формулировка категории Клейсли
Отметим, что мы могли бы сформулировать класс Kleisli и в более общем виде с помощью класса
Category:
class Kleisli m where
idK
:: Category cat => cat a (m a)
(*> ) :: Category cat => cat a (m b) -> cat b (m c) -> cat a (m c)
(+> ) :: (Category cat, Kleisli m)