haskell-notes

Теперь вспомним о категории Hask. В этой категории объектами являются типы, а стрелками функции.

Функтор f отображает объекты и стрелки категории Hask в объекты и стрелки f Hask. При этом оказывается,

что за счёт свойств функтора f Hask образует категорию.

• Объекты – это типы f a.

• Стрелки – это функции fmap f.

• Композиция стрелок это просто композиция функций.

• Тождественная стрелка это fmap id.

Проверим аксиомы:

fmap f . fmap id = fmap f . id = fmap f

fmap id . fmap f = id . fmap f = fmap f

fmap f . (fmap g . fmap h)

=

fmap f . fmap (g . h)

=

fmap (f . (g . h))

=

fmap ((f . g) . h)

=

fmap (f . g) . fmap h

=

(fmap f . fmap g) . fmap h

Функтор | 229

Видно, что аксиомы выполнены, так функтор f порождает категорию f Hask. Интересно, что поскольку

Hask содержит все типы, то она содержит и типы f Hask. Получается, что мы построили категорию внутри

категории. Это можно пояснить на примере списков. Тип [] погружает любой тип в список, а функцию для

любого типа можно превратить в функцию, которая работает на списках с помощью метода fmap. При этом с

помощью класса Functor мы проецируем все типы и все функции в мир списков [a]. Но сам этот мир списков

содержится в категории Hask.

С помощью функторов мы строим внутри одной категории другую категорию, при этом внутренняя ка-

тегория обладает некоторой структурой. Так если раньше у нас были только произвольные типы a и произ-

вольные функции a -> b, то теперь все объекты имеют тип [a] и все функции имеют тип [a] -> [b]. Также и

функтор Maybe переводит произвольное значение, в значение, которое обладает определённой структурой. В

нём выделен дополнительный элемент Nothing, который обозначает отсутствие значения. Если по типу val

:: a мы ничего не можем сказать о содержании значения val, то по типу val :: Maybe a, мы знаем один

уровень конструкторов. Например мы уже можем проводить сопоставление с образцом.

Теперь давайте вернёмся к теории категорий и дадим формальное определение понятия. Пусть A и B

категории, тогда функтором из A в B называют отображение F , которое переводит объекты A в объекты B

и стрелки A в стрелки B, так что выполнены следующие свойства:

F f

:

F A >B F B если f : A >A B

F idA

=

idF A

для любого объекта A из A

F ( f ; g)

=

F f ; F g

если ( f ; g) подходят по типам

Здесь запись >A и >B означает, что эти стрелки в разных категориях. После отображения стрелки f

из категории A мы получаем стрелку в категории B, это и отражено в типе F f : F A >B F B. Первое

свойство говорит о том, что после отображения стрелки соединяют те же объекты, что и до отображения.

Второе свойства говорит о сохранении тождественных стрелок. А последнее свойство, говорит о том, что

“пути” между объектами также сохраняются. Если мы находимся в категории A в объекте A и перед нами

есть путь состоящий из нескольких стрелок в объект B, то неважно как мы пойдём в F B либо мы пройдём

этот путь в категории A и в самом конце переместимся в F B или мы сначала переместимся в F A и затем

пройдём по образу пути в категории F B. Так и так мы попадём в одно и то же место. Схематически это

можно изобразить так:

f

g

A

B

C

F

F

F

F A

F B

F C

F f

F g

Стрелки сверху находятся в категории A, а стрелки снизу находятся в категории B. Функтор F : A > A,

который переводит категорию A в себя называют эндофунктором (endofunctor). Функторы отображают одни

категории в другие сохраняя структуру первой категории. Мы словно говорим, что внутри второй категории

есть структура подобная первой. Интересно, что последовательное применение функторов, также является

функтором. Мы будем писать последовательное применение функторов F и G слитно, как F G. Также можно

определить и тождественный функтор, который ничего не делает с категорией, мы будем обозначать его как

IA или просто I, если категория на которой он определён понятна из контекста. Это говорит о том, что мы

можем построить категорию, в которой объектами будут другие категории, а стрелками будут функторы.

15.3 Естественное преобразование

В программировании часто приходится переводить данные из одной структуры в другую. Каждая из

структур хранит какие-то конкретные значения, но мы ничего с ними не делаем мы просто перекладываем

содержимое из одного ящика в другой. Например в нашем ящике только один отсек, но вдруг нам пришло

бесконечно много подарков, что поделать нам приходится сохранить первый попавшийся, отбросив осталь-

ные. Главное в этой аналогии это то, что мы ничего не меняем, а лишь перекладываем содержимое из одной

структуры в другую.

В Haskell это можно описать так:

onlyOne :: [a] -> Maybe a

onlyOne []

= Nothing

onlyOne (a:as)

= Just a

В этой функции мы перекладываем элементы из списка [a] в частично определённое значение Maybe.

Тоже самое происходит и в функции concat:

230 | Глава 15: Теория категорий

concat :: [[a]] -> [a]

Элементы перекладываются из списка списков в один список. В теории категорий этот процесс называ-

ется естественным преобразованием. Структуры определяются функторами. Поэтому в определении будет

участвовать два функтора. В функции onlyOne это были функторы [] и Maybe. При перекладывании элемен-

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162