Читаем Учебник по Haskell полностью

В этом определении участвуют тип 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, мы знаем один

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

Теперь давайте вернёмся к теории категорий и дадим формальное определение понятия. Пусть 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)

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных