Является ли наличие `(a -› b) - ›b` эквивалентом` a`?

На чисто функциональном языке единственное, что вы можете сделать со значением, - это применить к нему функцию.

Другими словами, если вы хотите сделать что-нибудь интересное со значением типа a, вам понадобится функция (например) с типом f :: a -> b, а затем примените ее. Если кто-то передаст вам (flip apply) a с типом (a -> b) -> b, подходит ли это для замены a?

А как бы вы назвали что-то с типом (a -> b) -> b? Видя, что он заменяет a, у меня возникло бы искушение назвать его прокси-сервером или чем-то еще из http://www.thesaurus.com/browse/proxy.


person z5h    schedule 24.07.2017    source источник
comment
А затем вы можете взять f = id и получить фактический экземпляр a, или я что-то упускаю.   -  person harold    schedule 24.07.2017
comment
@harold действительно! Это было прямо перед моим лицом. Итак, (a -> b) -> b изоморфен a.   -  person z5h    schedule 24.07.2017
comment
Здесь есть двусмысленность. Имеется в виду полиморфный тип forall b. (a -> b) -> b или (a -> b) -> b для определенного b?   -  person luqui    schedule 24.07.2017
comment
Ср. Лемма Йонеды с F = Id.   -  person gallais    schedule 24.07.2017
comment
конечно, это iso, но только если игнорирование контроля не имеет большого значения. но это большое дело. ваш a может быть на другом конце планеты, заперт в клетке, ключ от которой находится в колодце, который держит старик и т. д.   -  person nicolas    schedule 26.07.2017
comment
И как бы вы назвали что-то с типом (a -> b) -> b - FWIW, мне нравится приостановленное вычисление или приостановка, следуя моей реплике из этого классического ответа .   -  person duplode    schedule 22.04.2018


Ответы (3)


Луки ответил превосходно, но я собираюсь предложить другое объяснение 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 использует плотность кода: оба bs обобщаются конструктором произвольного типа, который мы назовем 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
comment
Отличный материал. Теперь я думаю о type ApplicativeBox f a = forall b. f (a -> b) -> f b - person minimalis; 25.07.2017
comment
Для заинтересованных: reddit.com/rcomments/6skell/haskell/ / - person minimalis; 26.07.2017
comment
Если мне интересно обсуждение Reddit: не только b может быть таким же, как a; он может только быть a (если мы исключаем не завершающие выражения / нижние значения). Таким образом, тип forall b.(a -> b) -> b может быть создан только как (a -> a) -> a. Что, очевидно, эквивалентно a. - person AntC; 26.07.2017
comment
Нет это не правда. Вызывающий может выбрать любой b по своему усмотрению. Попробуй! - person Rein Henrichs; 26.07.2017
comment
@ReinHenrichs, в вашем ответе: мы не можем генерировать произвольные значения в Haskell. Таким образом, мы не можем сгенерировать произвольное значение для произвольного типа b. Мы могли выбрать b ~ Int; тогда получаем (a -> Int) -> Int. Мы могли бы удовлетворить это только с помощью некоторой постоянной функции, которая игнорирует a (скажем, const 17). Это не тип forall b.(a -> b) -> b. - person AntC; 26.07.2017
comment
Нет это не правда. Мы не можем сгенерировать произвольные значения, но мы не пытаемся сгенерировать произвольные значения, когда передаем блоку функцию. Функция генерирует значения. Учтите: box True (bool 1 2). Это буквально bool 1 2 True. Что блок не может сделать, так это вернуть 3 или другое произвольное значение. Он может дать вам (не нижнее) значение, только применив функцию. - person Rein Henrichs; 26.07.2017
comment
Теперь вы создали экземпляр a ~ Bool. Итак, у нас есть forall b. (Bool -> b) -> b. Я не вижу, чтобы это было параметрическим в a и forall b.. Вы говорите, что производитель коробки выбирает a, а пользователь коробки выбирает b. Пользователь коробки должен знать «заранее», что изготовитель коробки выбрал для a. Затем мы должны перегрузить функцию (или что-то еще), чтобы она зависела от выбора для a. - person AntC; 26.07.2017
comment
Пользователь коробки должен знать, что изготовитель коробки выбрал для a, потому что изготовитель коробки уже выбрал a, когда он делал коробку. Во всяком случае, нет смысла спорить об этом, когда можно было убедить себя, проведя 30 секунд в GHCi. - person Rein Henrichs; 26.07.2017
comment
Читая, что что-то просто flip ($), я просто должен добавить наблюдение (потому что я был так поражен, когда впервые понял это), что ($) просто id ограничено типами функций. - person Christian Sievers; 26.07.2017
comment
@ReinHenrichs, ТАК стал сердиться на нашу беседу и предложил обсудить это в чате. Я установил один (надеюсь ;-). Всех приветствую! chat.stackoverflow.com/rooms/ 150304 / - person AntC; 27.07.2017
comment
Кстати, unbox . box = id доказательство - простое. box . unbox = id требует предположения о параметричности, и поэтому не является столь же тривиальным доказательством. - person luqui; 02.07.2018
comment
@luqui, как ты думаешь, почему я это сделал? ;) - person Rein Henrichs; 14.07.2018

Этот вопрос открывает нам доступ к ряду более глубоких концепций.

Во-первых, обратите внимание, что в этом вопросе есть двусмысленность. Имеем ли мы в виду тип forall b. (a -> b) -> b, так что мы можем создать экземпляр b с любым типом, который нам нравится, или мы имеем в виду (a -> b) -> b для некоторых конкретных b, которые мы не можем выбрать.

Мы можем формализовать это различие в Haskell следующим образом:

newtype Cont b a = Cont ((a -> b) -> b)
newtype Cod a    = Cod (forall b. (a -> b) -> b)

Здесь мы видим некоторый словарный запас. Первый тип - это _6 _ монада, вторая - _7 _ Identity, хотя мое знакомство с последним термином недостаточно сильным, чтобы сказать то, что вы должны назвать по-английски.

Cont b a не может быть эквивалентом a, если a -> b не может содержать по крайней мере столько же информации, сколько a (см. Комментарий Дэна Робертсона ниже). Так, например, обратите внимание, что вы никогда ничего не сможете получить из Cont Void a.

Cod a эквивалентно a. Чтобы убедиться в этом, достаточно засвидетельствовать изоморфизм:

toCod :: a -> Cod a
fromCod :: Cod a -> a

чьи реализации я оставлю в качестве упражнения. Если вы действительно хотите это сделать, вы можете попытаться доказать, что эта пара действительно является изоморфизмом. fromCod . toCod = id легко, но toCod . fromCod = id требует свободной теоремы для Cod.

person luqui    schedule 24.07.2017
comment
Как намекнул Галле, Codensity Identity a также оказывается изоморфным Yoneda Identity a (Yoneda f a = forall b. (a -> b) -> f b), так что это хорошее место для начала изучения леммы Йонеды и расширений Кана. (Стоит отметить, что это естественное преобразование из (->) a в f.) Вы также можете думать о Йонеде как о соответствующем типе кодирования функтора Church / CPS. Я также не знаю, как бы вы это назвали простым английским языком, ха-ха - person Jon Purdy; 25.07.2017
comment
Я бы, наверное, сказал, что cont b a может быть эквивалентно a, даже если b содержит меньше информации, чем a. Важно то, сколько информации может вместить b^a. Например, любой счетный и рекурсивно перечислимый тип a с равенством эквивалентен cont a Bool, и если тип ограничен и поддерживает деление интервалов пополам (например, функции, переводящие натуральные числа в $ \ {0,1 \} $, т.е. вычислимые действительные числа в [0, 1] (примерно)), то он изоморфен Cont a Ord. - person Dan Robertson; 25.07.2017
comment
@DanRobertson, отличное наблюдение! Я записал это в ответе. - person luqui; 25.07.2017

Другие ответы отлично описали взаимосвязь между типами forall b . (a -> b) -> b и a, но я хотел бы указать на одно предостережение, потому что это приводит к некоторым интересным открытым вопросам, над которыми я работал.

Технически forall b . (a -> b) -> b и a не изоморфны в таком языке, как Haskell, который (1) позволяет вам писать выражение, которое не завершается, а (2) либо вызывается по значению (строгое), либо содержит seq. Я хочу здесь не придираться или показывать, что параметричность в Haskell ослаблена (как это хорошо известно), но что могут быть изящные способы усилить ее и в некотором смысле исправить изоморфизмы, подобные этому.

Есть некоторые термины типа forall b . (a -> b) -> b, которые нельзя выразить как a. Чтобы понять, почему, давайте начнем с доказательства, которое Рейн оставил в качестве упражнения box . unbox = id. Оказывается, это доказательство на самом деле более интересно, чем доказательство в его ответе, поскольку оно решающим образом опирается на параметричность.

box . unbox
= {- definition of (.) -}
  \m -> box (unbox m)
= {- definition of box -}
  \m f -> f (unbox m)
= {- definition of unbox -}
  \m f -> f (m id)
= {- free theorem: f (m id) = m f -}
  \m f -> m f
= {- eta: (\f -> m f) = m -}
  \m -> m
= {- definition of id, backwards -}
  id

Интересный шаг, на котором в игру вступает параметричность, - это применение теоремы о свободе f (m id) = m f. Это свойство является следствием forall b . (a -> b) -> b, типа m. Если мы думаем о m как о блоке с базовым значением типа a внутри, тогда единственное, что m может сделать со своим аргументом, - это применить его к этому базовому значению и вернуть результат. С левой стороны это означает, что f (m id) извлекает базовое значение из поля и передает его f. Справа это означает, что m применяется f непосредственно к базовому значению.

К сожалению, это рассуждение не совсем верно, когда у нас есть такие термины, как m и f ниже.

m :: (Bool -> b) -> b
m k = seq (k true) (k false)

f :: Bool -> Int
f x = if x then ⊥ else 2`

Напомним, мы хотели показать f (m id) = m f

f (m id)
= {- definition f -}
  if (m id) then ⊥ else 2
= {- definition of m -}
  if (seq (id true) (id false)) then ⊥ else 2
= {- definition of id -}
  if (seq true (id false)) then ⊥ else 2
= {- definition of seq -}
  if (id false) then ⊥ else 2
= {- definition of id -}
  if false then ⊥ else 2
= {- definition of if -}
  2

m f
= {- definition of m -}
  seq (f true) (f false)
= {- definition of f -}
  seq (if true then ⊥ else 2) (f false)
= {- definition of if -}
  seq ⊥ (f false)
= {- definition of seq -}
  ⊥

Ясно, что 2 не равно , поэтому мы потеряли нашу бесплатную теорему и изоморфизм между a и (a -> b) -> b с ней. Но что именно произошло? По сути, m - это не просто хорошо управляемый блок, потому что он применяет свой аргумент к двум разным базовым значениям (и использует seq, чтобы гарантировать, что оба этих приложения фактически оцениваются), что мы можем наблюдать, передавая продолжение, которое завершается на одном из эти основные ценности, но не другие. Другими словами, m id = false на самом деле не является точным представлением m как Bool, потому что он «забывает» тот факт, что m вызывает свой ввод с помощью обоих true и false.

Проблема является результатом взаимодействия трех вещей:

  1. Наличие неопределенности.
  2. Наличие seq.
  3. Тот факт, что термины типа forall b . (a -> b) -> b могут применяться несколько раз.

Нет особой надежды обойти пункты 1 или 2. Однако линейные типы могут дать нам возможность решить третью проблему. Линейная функция типа a ⊸ b - это функция от типа a до типа b, которая должна использовать свой ввод ровно один раз. Если мы требуем, чтобы m имел тип forall b . (a -> b) ⊸ b, это исключает наш контрпример к теореме о свободе и должно позволить нам показать изоморфизм между a и forall b . (a -> b) ⊸ b даже при наличии неопределенности и seq.

Это действительно круто! Это показывает, что линейность может «спасти» интересные свойства, подавляя эффекты, которые могут затруднить истинное уравнение рассуждений.

Однако остается одна большая проблема. У нас пока нет методов, чтобы доказать теорему о свободе, которая нам нужна для типа forall b . (a -> b) ⊸ b. Оказывается, текущие логические отношения (инструменты, которые мы обычно используем для таких доказательств) не были разработаны для того, чтобы учесть линейность так, как это необходимо. Эта проблема имеет значение для определения корректности компиляторов, выполняющих переводы CPS.

person Nick Rioux    schedule 25.07.2017