Есть такой раздел математики — теория категорий, и ложится она на всё, что мы обсуждаем, как нельзя лучше, — так что мы просто обязаны немного о ней поговорить. Мысль в её основе вызывающе проста: забыть, что лежит внутри математических структур, и смотреть только на связи между ними. Работает теория всего с двумя видами сущностей. Первые — объекты: ; что они такое, категорию не интересует. Вторые — морфизмы, они же стрелки: у каждой есть источник и цель, .
Стрелки могут склеиваться: например, для и определена композиция . Дальше — две аксиомы. Первая: композиция ассоциативна, . Вторая: у каждого объекта есть тождественная стрелка , нейтральная к композиции: . Объекты, стрелки и две аксиомы — это и есть категория.
В Haskell этот паттерн выделен в класс типов — из модуля :
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
-- законы класса — в точности аксиомы категории:
f . id == f
id . f == f
(f . g) . h == f . (g . h)Здесь — сама стрелка, параметризованная источником и целью (род ). В общем-то самый всеобъемлющий инстанс категории — это : объекты — типы Haskell, морфизмы — функции между ними, композиция — из Prelude, тождество — :
instance Category (->) where
id x = x
(g . f) x = g (f x)
show :: Int -> String -- морфизм Int -> String
length :: String -> Int -- морфизм String -> Int
length . show :: Int -> Int -- их композиция — тоже морфизмНастоящая ценность теории категорий для нас — то, что все конструкции, которые мы обсудили в этой главе, формулируются на её языке очень чисто и чётко:
- моноид — категория с одним объектом: морфизмы — элементы моноида, — композиция, — тождество;
- функтор — отображение категории в категорию: объекты переходят в объекты, стрелки — в стрелки, а тождество и композиция сохраняются (это в точности законы ); наши функторы переводят в него же, поэтому зовутся эндофункторами — приставка «эндо-» по-гречески значит «внутри»: функтор из категории в неё же саму;
- аппликативный функтор — моноидальный функтор: помимо стрелок он переносит и пары, склеивает в , а даёт единицу;
- монада — категория стрелок Клейсли с композицией и тождеством ;
- Foldable — свёртка в категорию с одним объектом: увозит элементы структуры в моноид и там склеивает;
- Traversable — перестановка двух эндофункторов местами: .
Пункт про монаду можно потрогать руками: категория Клейсли — это newtype-обёртка с инстансом :
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
id = Kleisli pure
Kleisli g . Kleisli f = Kleisli (f >=> g)
-- знакомый half — теперь как стрелка Клейсли:
half :: Kleisli Maybe Int Int
half = Kleisli (\x -> if even x then Just (x `div` 2) else Nothing)
quarter :: Kleisli Maybe Int Int
quarter = half . half -- обычная точка склеила эффектные стрелки
runKleisli quarter 12 -- Just 3
runKleisli quarter 6 -- Nothing (3 нечётно — вторая половинка не взялась)Проверить законы для этого инстанса — значит проверить законы монады: тождество и ассоциативность мы уже видели в разделе о стрелках Клейсли. Монада и её категория Клейсли восстанавливаются друг из друга: это одна конструкция, записанная двумя способами.
К Haskell мы пришли тропой λ-исчисления: термы, редукции, типы. Но можно было идти и с другой стороны: типизированное λ-исчисление и декартово замкнутые категории — один и тот же предмет на двух языках. Тогда типы — это объекты, программы — морфизмы, а конструкции этой главы — категорные определения.