В этом определении участвуют тип f и метод fmap. Можно сказать, что тип f переводит произвольные
типы a в специальные типы f a. В этом смысле тип f является функцией, которая определена на типах. Метод
fmap переводит функции общего типа a -> b в специальные функции f a -> f b.
При этом должны выполняться свойства:
fmap id
= id
fmap (f . g) = fmap f . fmap g
Теперь вспомним о категории 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, мы знаем один
уровень конструкторов. Например мы уже можем проводить сопоставление с образцом.
Теперь давайте вернёмся к теории категорий и дадим формальное определение понятия. Пусть
категории, тогда
и стрелки
:
=
для любого объекта
=
если (
Здесь запись
из категории
свойство говорит о том, что после отображения стрелки соединяют те же объекты, что и до отображения.
Второе свойства говорит о сохранении тождественных стрелок. А последнее свойство, говорит о том, что
“пути” между объектами также сохраняются. Если мы находимся в категории
есть путь состоящий из нескольких стрелок в объект
этот путь в категории
пройдём по образу пути в категории
можно изобразить так:
Стрелки сверху находятся в категории
который переводит категорию
категории в другие сохраняя структуру первой категории. Мы словно говорим, что внутри второй категории
есть структура подобная первой. Интересно, что последовательное применение функторов, также является
функтором. Мы будем писать последовательное применение функторов
определить и тождественный функтор, который ничего не делает с категорией, мы будем обозначать его как
можем построить категорию, в которой объектами будут другие категории, а стрелками будут функторы.
15.3 Естественное преобразование
В программировании часто приходится переводить данные из одной структуры в другую. Каждая из
структур хранит какие-то конкретные значения, но мы ничего с ними не делаем мы просто перекладываем
содержимое из одного ящика в другой. Например в нашем ящике только один отсек, но вдруг нам пришло
бесконечно много подарков, что поделать нам приходится сохранить первый попавшийся, отбросив осталь-
ные. Главное в этой аналогии это то, что мы ничего не меняем, а лишь перекладываем содержимое из одной
структуры в другую.
В Haskell это можно описать так:
onlyOne :: [a] -> Maybe a
onlyOne []
= Nothing
onlyOne (a:as)