Если присмотреться к типам функций, можно понять, что тип-экземпляр cat принимает два параметра.
Совсем как тип функции (a -> b). Формально его можно записать в префиксной форме так (-> ) a b.
Получается, что тип cat это что-то вроде функции. Это некоторые сущности, у которых есть понятия
тождества и композиции.
Для обычных функций экземпляр класса Category уже определён. Но в этом модуле у нас есть ещё и
необычные функции, функции которые преобразуют ленты значений. Функции id и (. ) мы определим,
сделав наш тип St экземпляром класса Category. Также определите постоянный преобразователь. Он
на любой вход возвращает одно и то же число, и преобразователь, который будет накапливать сумму
поступающих на вход значений, по-другому такой преобразователь называют интегратором:
const
:: a -> St b a
integral :: Num a => St a a
• Перепишите с помощью fix несколько стандартных функций для списков. Например map, foldr, foldl,
zip, repeat, cycle, iterate.
Старайтесь найти наиболее краткое выражение, пользуйтесь функциями высшего порядка и частичным
применением. Например рассмотрим функцию repeat:
repeat :: a -> [a]
repeat a = a : repeat a
Запишем с fix:
repeat a = fix $ \xs -> a : xs
Заметим, что мы можем избавиться от аргумента xs с помощью сечения:
repeat a = fix (a:)
Но мы можем пойти ещё дальше, если вспомним, что функция двух аргументов (:) является функцией
от одного аргумента (:) :: a -> ([a] -> [a]), которая возвращает функцию одного аргумента:
repeat = fix . (:)
Смотрите в этом выражении мы составили композицию двух функций. Функция (:) примет первый
аргумент и вернёт функцию, как раз то, что и нужно для fix.
Упражнения | 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)