«Подъем» много раз встречается в функциональном программировании, не только в fmap
, но и во многих других контекстах. Примеры «лифтинга» включают:
fmap :: (a -> b) -> F a -> F b
где F
- функтор
cmap :: (b -> a) -> F a -> F b
, где F
- подрядчик
bind :: (a -> M b) -> M a -> M b
где M
- монада
ap :: F (a -> b) -> F a -> F b
где F
- аппликативный функтор
point :: (_ -> a) -> _ -> F a
где F
- точечный функтор
filtMap :: (a -> Maybe b) -> F a -> F b
где F
- фильтруемый функтор
extend :: (M a -> b) -> M a -> M b
, где M
- комонада
Другие примеры включают аппликативный контрафунктор, фильтруемый контрафунктор и совмещенный функтор.
Все эти сигнатуры типов похожи в одном: они отображают один вид функции между a
и b
в другой вид функции между a
и b
.
В этих разных случаях типы функций не просто a -> b
, но имеют какие-то «скрученные» типы: например, a -> M b
или F (a -> b)
, или M a -> b
, или F a -> F b
и так далее. Однако каждый раз законы очень похожи: типы скрученных функций должны иметь законы идентичности и композиции, а скрученная композиция должна быть ассоциативной.
Например, для аппликативных функторов нам нужно уметь составлять функции типа F (a -> b)
. Итак, нам нужно определить специальную функцию «скрученной» идентичности (pure id :: F (a -> a)
) и операцию «скрученной» композиции, назовите ее apcomp
с сигнатурой типа F (a -> b) -> F (b -> c) -> F (a -> c)
. Эта операция должна иметь законы идентичности и ассоциативности. Операция ap
должна иметь законы идентичности и композиции («карты искаженной идентичности в искаженную идентичность» и «карты искаженной композиции в искаженную композицию»).
После того как мы пройдемся по всем этим примерам и выведем законы, мы сможем доказать, что законы оказываются одинаковыми во всех случаях, если мы сформулируем законы с помощью «скрученных» операций.
Это потому, что мы можем сформулировать все эти операции как функторы в смысле теории категорий. Например, для аппликативного функтора мы определяем две категории: F-аппликативная категория (объекты a
, b
, ..., морфизмы F(a -> b)
) и F-поднятая категория (объекты F a
, F b
, ..., морфизмы F a -> F b
) . Функтор между этими двумя категориями требует от нас подъема морфизмов, ap :: F(a -> b) -> F a -> F b
. Законы ap
полностью эквивалентны стандартным законам этого функтора.
Подобные аргументы справедливы и для других классов типов. Нам необходимо определить категории, морфизмы, операции композиции, тождественные морфизмы и функторы в каждом случае. Убедившись, что законы выполняются, мы увидим, что каждый из этих классов типов имеет связанную пару категорий и функтор между ними, так что законы класса типов эквивалентны законам этих категорий и функтора.
Что мы получили? Мы сформулировали законы многих классов типов таким же образом (как законы категорий и функторов). Это великая экономия мысли: нам не нужно каждый раз запоминать все эти законы; мы можем просто запомнить, какие категории и какие функторы необходимо записать для каждого класса типов, при условии, что методы этого класса могут быть сведены к некоему «извилистому подъему».
Таким образом, мы можем сказать, что «лифтинги» важны и обеспечить применение теории категорий в функциональном программировании.
Я сделал презентацию об этом, https://www.youtube.com/watch?v=Zau8CxsfxOo, и я пишу новую бесплатную книгу, в которой будут показаны все производные. https://github.com/winitzki/sofp
person
winitzki
schedule
07.12.2019