Что такое определение Аппликативного Функтора из теории категорий POV?
Я смог отобразить определение Функтора из теории категорий в определение Хаскелла следующим образом: поскольку объекты Hask
типы, функтор F
- карты каждого типа
a
изHask
к новому типуF a
грубо говоря, добавив к нему "F ". - наносит на карту каждый морфизм
a -> b
изHask
к новому морфизмуF a -> F b
с помощьюfmap :: (a -> b) -> (f a -> f b)
,
Все идет нормально. Теперь я добираюсь до Applicative
и не могу найти упоминаний о такой концепции в учебниках. Глядя на то, что это добавляет к Functor
, ap :: f (a -> b) -> f a -> f b
Я попытался придумать свое собственное определение.
Во-первых, я заметил, что с (->)
также тип, морфизмы Hask
являются объектами этого тоже. В свете этого я высказал предположение, что аппликативный функтор является функтором, который также может отображать "стрелочные"-объекты исходной категории в морфизмы целевой.
Это правильная интуиция? Можете ли вы дать более формальное и строгое определение?
2 ответа
Ключ к пониманию аппликативных функторов - выяснить, какую структуру они сохраняют.
Регулярные функторы сохраняют базовую категориальную структуру: они отображают объекты и морфизмы между категориями и сохраняют законы категории (ассоциативность и идентичность).
Но категория может иметь больше структуры. Например, он может разрешить определение отображений, которые похожи на морфизмы, но принимают несколько аргументов. Такие отображения определяются с помощью каррирования: например, функция двух аргументов определяется как функция одного аргумента, возвращающего другую функцию. Это возможно, если вы можете определить объект, который представляет тип функции. В общем, этот объект называется экспоненциальным (в Haskell это просто тип b->c
). Затем мы можем иметь морфизмы от одного объекта к экспоненте и называть его морфизмом с двумя аргументами.
Традиционное определение аппликативного функтора в Haskell основано на идее отображения функций нескольких аргументов. Но есть эквивалентное определение, которое разбивает функцию с несколькими аргументами вдоль другой границы. Вы можете посмотреть на такую функцию, как отображение продукта (пара в Haskell) на другой тип (здесь, c
).
a -> (b -> c) ~ (a, b) -> c
Это позволяет нам рассматривать аппликативные функторы как функторы, сохраняющие продукт. Но продукт - это только один пример того, что называется моноидальной структурой.
Обычно моноидальная категория - это категория, снабженная тензорным произведением и единичным объектом. В Хаскеле это может быть, например, декартово произведение (пара) и тип единицы ()
, Заметьте, однако, что моноидальные законы (законы ассоциативности и единицы) действительны только с точностью до изоморфизма. Например:
(a, ()) ~ a
Аппликативный функтор может быть определен как функтор, сохраняющий моноидальную структуру. В частности, это должно сохранить устройство и продукт. Не должно иметь значения, делаем ли мы "умножение" до или после применения функтора. Результаты должны быть изоморфными.
Однако нам не нужен полноценный моноидальный функтор. Все, что нам нужно, это два морфизма (в отличие от изоморфизмов) - один для умножения и один для единицы. Такой функтор, который наполовину сохраняет моноидальную структуру, называется слабым моноидальным функтором. Отсюда и альтернативное определение:
class Functor f => Monoidal f where
unit :: f ()
(**) :: f a -> f b -> f (a, b)
Легко показать, что Monoidal
эквивалентно Applicative
, Например, мы можем получить pure
от unit
и наоборот:
pure x = fmap (const x) unit
unit = pure ()
Прикладные законы вытекают просто из сохранения моноидных законов (ассоциативных и единичных законов).
В теории категорий сохранение моноидальной структуры связано с тензорной прочностью, поэтому аппликативный функтор также известен как сильный слабый моноидальный функтор. Однако в Hask каждый функтор обладает канонической силой по отношению к продукту, поэтому это свойство ничего не добавляет к определению.
Теперь, если вы знакомы с определением монады как моноида в категории эндофункторов, вам может быть интересно узнать, что аппликативы аналогично являются моноидами в категории эндофункторов, где тензорным произведением является свертка Дня. Но это гораздо сложнее объяснить.
Ты прав, Applicative
переводит менее просто, чем Functor
или же Monad
, Но по сути это класс моноидальных функторов:
class Functor f => Monoidal f where
pureUnit :: f ()
fzip :: f a -> f b -> f (a,b)
Из этого вы можете определить - в Hask -
pure x = fmap (const x) pureUnit
а также
fs <*> xs = fmap (uncurry ($)) $ fzip fs xs