Луки ответил превосходно, но я собираюсь предложить другое объяснение forall b. (a -> b) -> b === a
по нескольким причинам: во-первых, потому что я думаю, что обобщение на Code density немного излишне восторженно. А во-вторых, потому что это возможность связать кучу интересного воедино. Вперед!
Волшебная шкатулка z5h
Представьте, что кто-то подбросил монету, а затем положил ее в волшебную шкатулку. Вы не можете видеть внутри коробки, но если вы выберете тип b
и передадите в поле функцию с типом Bool -> b
, коробка выдаст b
. Что мы можем узнать об этой коробке, не заглядывая в нее? Можем ли мы узнать, в каком состоянии находится монета? Можем ли мы узнать, какой механизм использует коробка для производства b
? Оказывается, мы можем сделать и то, и другое.
Мы можем определить блок как функцию ранга 2 типа Box Bool
, где
type Box a = forall b. (a -> b) -> b
(Здесь тип ранга 2 означает, что производитель коробки выбирает a
, а пользователь коробки выбирает b
.)
Мы помещаем a
в поле, а затем закрываем его, создавая ... закрытие.
-- Put the a in the box.
box :: a -> Box a
box a f = f a
Например, box True
. Частичное приложение - это просто умный способ создать закрытие!
Итак, решка или решка? Поскольку я, пользователь ящика, могу выбрать b
, я могу выбрать Bool
и передать функцию Bool -> Bool
. Если я выберу id :: Bool -> Bool
, тогда возникает вопрос: выдаст ли коробка то значение, которое она содержит? Ответ заключается в том, что коробка либо выдаст содержащееся в ней значение, либо выдаст чушь (нижнее значение, например undefined
). Другими словами, если вы получили ответ, то он должен быть правильным.
-- Get the a out of the box.
unbox :: Box a -> a
unbox f = f id
Поскольку мы не можем генерировать произвольные значения в Haskell, единственная разумная вещь, которую может сделать блок, - это применить данную функцию к значению, которое он скрывает. Это следствие параметрического полиморфизма, также известного как параметричность.
Теперь, чтобы показать, что Box a
изоморфен a
, нам нужно доказать две вещи относительно упаковки и распаковки. Нам нужно доказать, что вы получаете то, что вкладываете, и что вы можете вкладывать то, что получаете.
unbox . box = id
box . unbox = id
Я сделаю первое, а второе оставлю читателю в качестве упражнения.
unbox . box
= {- definition of (.) -}
\b -> unbox (box b)
= {- definition of unbox and (f a) b = f a b -}
\b -> box b id
= {- definition of box -}
\b -> id b
= {- definition of id -}
\b -> b
= {- definition of id, backwards -}
id
(Если эти доказательства кажутся довольно тривиальными, это потому, что все (полные) полиморфные функции в Haskell являются естественными преобразованиями, и то, что мы здесь доказываем, - это естественность. Параметричность снова дает нам теоремы для низких, низких цен !)
В качестве отступления и еще одно упражнение для читателя, почему я на самом деле не могу определить rebox
с помощью (.)
?
rebox = box . unbox
Почему мне нужно вставить определение (.)
самого себя как какого-то пещерного человека?
rebox :: Box a -> Box a
rebox f = box (unbox f)
(Подсказка: какие типы box
, unbox
и (.)
?)
Идентичность, кодовая плотность и Йонеда, о боже!
Теперь, как мы можем обобщить Box
? luqui использует плотность кода: оба b
s обобщаются конструктором произвольного типа, который мы назовем f
. Это преобразование f a
в коде плотности.
type CodenseBox f a = forall b. (a -> f b) -> f b
Если мы исправим f ~ Identity
, то вернемся Box
. Однако есть и другой вариант: мы можем выбрать только тип возврата с помощью f
:
type YonedaBox f a = forall b. (a -> b) -> f b
(Я как бы отдал игру под этим названием, но мы еще вернемся к этому.) Мы также можем исправить f ~ Identity
здесь, чтобы восстановить Box
, но мы позволяем пользователю коробки передавать обычную функцию, а не стрелку Клейсли. . Чтобы понять, что мы обобщаем, давайте еще раз посмотрим на определение box
:
box a f = f a
Ну, это просто flip ($)
, не так ли? И оказывается, что наши два других блока построены путем обобщения ($)
: CodenseBox
- это частично примененное, перевернутое монадическое связывание, а YonedaBox
- частично примененное flip fmap
. (Это также объясняет, почему Codensity f
является Monad
, а Yoneda f
Functor
для любого выбора f
: единственный способ создать его - закрыть привязку или fmap, соответственно.) Кроме того, оба эти Концепции эзотерической теории категорий на самом деле являются обобщением концепции, знакомой многим работающим программистам: преобразование CPS!
Другими словами, YonedaBox
- это вложение Йонеды, а правильно абстрагированные _55 _ / _ 56_ законы для YonedaBox
являются доказательством леммы Йонеды!
TL; DR:
forall b. (a -> b) -> b === a
является примером леммы Йонеды.
person
Rein Henrichs
schedule
24.07.2017
f = id
и получить фактический экземплярa
, или я что-то упускаю. - person harold   schedule 24.07.2017(a -> b) -> b
изоморфенa
. - person z5h   schedule 24.07.2017forall b. (a -> b) -> b
или(a -> b) -> b
для определенногоb
? - person luqui   schedule 24.07.2017F = Id
. - person gallais   schedule 24.07.2017a
может быть на другом конце планеты, заперт в клетке, ключ от которой находится в колодце, который держит старик и т. д. - person nicolas   schedule 26.07.2017(a -> b) -> b
- FWIW, мне нравится приостановленное вычисление или приостановка, следуя моей реплике из этого классического ответа а>. - person duplode   schedule 22.04.2018