«Доказательство - это программа; формула, которую он доказывает, является типом для программы »

37

Это может быть философский вопрос, но я считаю, что на него есть объективный ответ.

Если вы читаете статью в Википедии о Haskell, вы можете найти следующее:

Этот язык основан на наблюдениях Хаскелла Карри и его интеллектуальных потомков, что «доказательство - это программа; формула, которую он доказывает, - это тип для программы»

Теперь я спрашиваю: разве это не применимо практически ко всем языкам программирования? Какая особенность (или набор функций) Haskell делает его совместимым с этим утверждением? Другими словами, каковы заметные способы, которыми это утверждение повлияло на структуру языка?


источник
4
Кто-нибудь хочет объяснить, почему «закрытые» голоса, пожалуйста?
1
@Grigory Javadyan: Я не голосовал за закрытие, но это, вероятно, потому, что вопрос не является темой для SO - философские вопросы, объективно отвечающие или нет, здесь вообще не подходят. В этом случае я думаю, что это оправданно, потому что ответ имеет глубокие практические последствия для того, как на самом деле используется Haskell.
2
@ Григорий: если этот вопрос был реальной проблемой с реальным решением (в коде), то он может остаться на SO. Проголосовал закрыть и перейти к программистам.
9
Просто чтобы добавить к этому, потому что я немного взволнован - ответы на эти вопросы изобилуют ссылками на сложные исследования CS и в этом смысле более «объективны», чем 90% SO. Кроме того, критерии Sixlettervariable (что для решения нужен код) безумно узки для широкого круга подлинных вопросов программирования, которые не являются ни субъективными, ни не по теме. Я действительно не хотел бы, чтобы дебаты о включении / удалении возобновились на SO, но если ясно, что потоки программирования, подобные этому, сталкиваются, то я волнуюсь ...
sclv
2
У меня двойственное отношение к тому, чем все это заканчивается, в основном потому, что мне действительно неясно, какой контент должен быть на Programmers.SE против SO. Но я скажу, что в нескольких местах программисты описываются как «субъективные вопросы», которыми этот вопрос категорически не является . Мой ответ о том, насколько неформальный и волнистый, насколько это возможно, и я все еще мог бы легко подтвердить большинство из них ссылками, которые примут даже встревоженные редакторы Википедии.
CA Макканн

Ответы:

38

Да, основная концепция применяется повсеместно, да, но редко полезным способом.

Начнем с того, что с точки зрения теории типов это предполагает, что «динамические» языки лучше всего рассматривать как имеющие один тип, который содержит (среди прочего) метаданные о природе значения, которое видит программист, включая то, что эти динамические языки будут называть сами по себе "тип" (что не является концептуально одним и тем же). Любые такие доказательства, вероятно, будут неинтересны, поэтому эта концепция в основном относится к языкам со статическими системами типов.

Кроме того, многие языки, которые предположительно имеют «статическую систему типов», должны рассматриваться как динамические на практике, в этом контексте, поскольку они допускают проверку и преобразование типов во время выполнения. В частности, это означает любой язык со встроенной, по умолчанию поддержкой «отражения» или тому подобное. C #, например.

Haskell необычен в том, сколько информации он ожидает от типа - в частности, функции не могут зависеть ни от каких значений, кроме тех, которые указаны в качестве аргументов. С другой стороны, в языке с изменяемыми глобальными переменными любая функция может (потенциально) проверять эти значения и соответственно изменять поведение. Таким образом, функцию Haskell с типом A -> Bможно рассматривать как миниатюрную программу, доказывающую, что это Aподразумевает B; эквивалентная функция во многих других языках только скажет нам, что Aи какое бы глобальное состояние ни находилось в области видимости, подразумевается B.

Обратите внимание, что хотя Haskell поддерживает такие вещи, как отражение и динамические типы, использование таких функций должно указываться в сигнатуре типа функции; аналогично для использования глобального государства. Ни один не доступен по умолчанию.

В Haskell также есть способы сломать вещи, например, разрешить исключения во время выполнения или использовать нестандартные примитивные операции, предоставляемые компилятором, но те ожидают, что они будут использоваться с полным пониманием только теми способами, которые победили. повредить смысл внешнего кода. Теоретически то же самое можно сказать и о других языках, но на практике с большинством других языков труднее выполнять задачи без «мошенничества» и менее навязчиво «обманывать». И, конечно, в настоящих «динамических» языках все это не имеет значения.

Концепция может быть взята гораздо дальше, чем в Хаскеле.

CA Макканн
источник
Обратите внимание, что исключения могут быть полностью интегрированы в систему типов.
садовник
18

Вы правы, что переписка Карри-Ховарда - вещь очень общая. Стоит немного ознакомиться с его историей: http://en.wikipedia.org/wiki/Curry-Howard_correspondence

Вы заметите, что в первоначальной формулировке это соответствие применимо, в частности, к интуиционистской логике с одной стороны и к простейшему типу лямбда-исчисления (STLC) с другой.

Классический Haskell - либо '98, либо даже более ранние версии, очень тесно связан с STLC, и по большей части был очень простой прямой перевод между любым данным выражением в Haskell и соответствующим термином в STLC (расширен с рекурсией и несколько примитивных типов). Так что это сделало Карри-Говарда очень явным. Сегодня, благодаря расширениям, такой перевод является несколько более сложным делом.

Таким образом, в некотором смысле, вопрос заключается в том, почему Хаскелл так легко «впадает» в STLC. На ум приходят две вещи:

  • Типы. В отличие от Схемы, которая также является своего рода подслащенным лямбда-исчислением (среди прочего), Хаскелл строго типизирован. Это означает, что в классическом Haskell не существует терминов, которые по определению не могут быть типизированными терминами в STLC.
  • Чистота. Опять же, в отличие от Scheme, но, как и в STLC, Haskell является чистым ссылочным прозрачным языком. Это довольно важно. Языки с побочными эффектами могут быть встроены в языки, которые не имеют побочных эффектов. Однако это трансформация всей программы, а не просто локальная десагеринг. Таким образом, чтобы иметь прямое соответствие, необходимо начинать с чисто функционального языка.

Есть также важный способ, которым Haskell, как и большинство языков, терпит неудачу в отношении прямого применения корреспонденции Карри-Ховарда. Haskell, как полный по Тьюрингу язык, содержит возможность неограниченной рекурсии и, следовательно, не прекращения. STLC не имеет оператора с фиксированной точкой, не является завершающим по Тьюрингу и сильно нормализуется - то есть, ни одно сокращение термина в STLC не завершится. Возможность рекурсии означает, что можно «обмануть» Карри-Говарда. Например, let x = x in xимеет типforall a. aТо есть, поскольку он никогда не возвращается, я могу притворяться, что он дает мне все, что угодно! Поскольку мы всегда можем сделать это в Haskell, это означает, что мы не можем полностью «поверить» в любое доказательство, соответствующее программе на Haskell, если у нас нет отдельного доказательства того, что сама программа завершает работу.

Происхождение функционального программирования до Хаскелла (особенно семейства ML) было результатом исследований CS, сфокусированных на создании языков, о которых вы могли легко доказать (среди прочего), исследованиях, которые очень хорошо знакомы и вытекают из CH с самого начала. И наоборот, Haskell служил как языком-носителем, так и источником вдохновения для ряда разрабатываемых помощников по доказательствам, таких как Agda и Epigram, которые основаны на разработках в теории типов, в значительной степени связанных с происхождением CH.

sclv
источник
1
Возможно, было бы хорошо подчеркнуть, что отсутствие термина подрывает доказательство определенным образом, которое, хотя и является явно катастрофическим с логической точки зрения, сохраняет многие другие свойства. В частности, функция A -> B, заданная как A, будет либо производить, Bлибо ничего вообще. Он никогда не выдаст C, и какое значение типа Bон предоставляет или, если он расходится, все равно будет зависеть исключительно от Aпредоставленного.
@camccann - немного придирчиво, но я бы различал дно и «вообще ничего», что больше похоже Void, нет? Лень усложняет и то и другое. Я бы сказал, что функция A -> B всегда производит значение типа B, но это значение может содержать меньше информации, чем можно было бы ожидать.
sclv
Nitpicking это весело! Когда я говорю «ничего», я имею в виду на уровне ценности в контексте выполнения оценки, тогда как дно действительно существует только как абстракция, а не как нечто осязаемое. Выражаемое выражение никогда не «увидит» значение bottom, только термины, которые оно не использует (которые могут быть снизу), и термины, которые оно использует (которые имеют не нижние значения). Попытка использовать нижний «никогда не происходит» в некотором смысле, потому что попытка сделать это завершает оценку всего выражения до того, как произойдет использование.
12

В приближении первого порядка большинство других (слабо и / или однотипированных) языков не поддерживают строгое разграничение на уровне языка между

  • предложение (то есть тип)
  • доказательство (т.е. программа демонстрирующее , как мы можем построить предложение из набора примитивов и / или других высших порядков конструкций)

и строгие отношения между ними. Во всяком случае, лучшими гарантиями, которые предоставляют другие такие языки, являются:

  • учитывая ограниченное ограничение на ввод, наряду с тем, что происходит в среде в то время, мы можем создать значение с ограниченным ограничением. (традиционные статические типы, ср. C / Java)
  • каждая конструкция имеет один и тот же тип (динамические типы, cf ruby ​​/ python)

Обратите внимание, что по типу мы ссылаемся на предложение и, следовательно, на нечто, описывающее гораздо больше информации, чем просто int или bool . В Haskell существует проникающая культура функции, на которую влияют только ее аргументы - без исключений *.

Чтобы быть немного более строгим, общая идея состоит в том, что, применяя жесткий интуиционистский подход (почти) ко всем программным конструкциям (т.е. мы можем доказать только то, что мы можем построить), и ограничивая набор примитивных конструкций в таком так, что мы имеем

  • строгие предложения для всех языковых примитивов
  • ограниченный набор механизмов, с помощью которых можно комбинировать примитивы

Конструкции на Haskell очень хорошо поддаются рассуждениям об их поведении. Если мы можем построить доказательство (читай: функция), доказывающее, что это Aподразумевает B, это имеет очень полезные свойства:

  • это всегда верно (пока у нас есть A, мы можем построить B)
  • этот вывод основывается только на A, и ничего другого.

что позволяет нам эффективно рассуждать о локальных / глобальных инвариантах. Вернемся к исходному вопросу; Языковые особенности Haskell, которые лучше всего способствуют этому мышлению:

  • Чистота / Сегментация эффектов в явные конструкции (эффекты учитываются и типизируются!)
  • Вывод типа / Проверка в компиляторах Haskell
  • Возможность встраивать инварианты управления и / или потока данных в предложения / типы, которые программа намеревается доказать: (с полиморфизмом, семействами типов, GADT и т. Д.)
  • Ссылочная целостность

Ничто из этого не является уникальным для Хаскелла (многие из этих идей невероятно стары). Тем не менее, в сочетании с богатым набором абстракций в стандартных библиотеках (обычно в классах типов), различным разделением на уровне синтаксиса и строгим стремлением к чистоте при разработке программ, мы получаем язык, который каким-то образом может быть одновременно достаточно практичен для реальных приложений , но в то же время оказывается проще рассуждать о большинстве традиционных языков.

Этот вопрос заслуживает достаточно глубокого ответа, и я не мог бы сделать это справедливо в этом контексте. Я бы посоветовал прочитать больше в Википедии / в литературе:

* Примечание: я игнорирую / игнорирую некоторые хитрые аспекты примесей Хаскелла (исключения, недопущения и т. Д.), Которые только усложняют аргумент.

Raeez
источник
4

Какая особенность? Система типов (будучи статичной, чистой, полиморфной). Хорошей отправной точкой являются «Теоремы Вадлера бесплатно». Заметное влияние на дизайн языка? Тип IO, классы типа.

JA.
источник
0

Иерархия Клини показывает нам , что доказательства не являются программами.

Первое рекурсивное отношение:

R1( Program , Iteration )  Program halts at Iteration.
R2( Theorem , Proof ) Proof proves a Theorem.

Первыми рекурсивно перечислимыми отношениями являются:

(exists x) R1( Program , x )  Program Halts.
(exists x) R2( Theorem , x)   Theorem is provable.

Таким образом, программа является теоремой, и существующая итерация, на которой программа останавливается, подобна существующему доказательству, доказывающему теорему.

Program = Theorem
Iteration = Proof

Когда программа правильно создана из спецификации, мы должны быть в состоянии доказать, что она удовлетворяет спецификации, и если мы можем доказать, что программа удовлетворяет спецификации, то это правильный синтез программы. Таким образом, мы выполняем программный синтез, если докажем, что программа удовлетворяет спецификации. Теорема о том, что программа удовлетворяет спецификации, является программой, в которой теорема относится к синтезированной программе.

Ложные выводы Мартина Лофа никогда не создавали компьютерных программ, и удивительно, что люди считают, что это методология синтеза программ. Полных примеров синтезируемой программы не было. Спецификация, такая как «ввод типа и вывод программы такого типа», не является функцией. Существует множество таких программ, и случайным образом выбрать одну из них не является рекурсивной функцией или даже функцией. Это просто глупая попытка показать синтез программы с помощью глупой программы, которая не представляет собой настоящую компьютерную программу, вычисляющую рекурсивную функцию.

Чарли Фольксторф
источник
2
Как этот ответ отвечает на вопрос: «Каковы заметные способы, которыми это утверждение повлияло на структуру языка?»
Гнат
1
@gnat - этот ответ обращается к основному предположению в исходном вопросе, а именно: " doesn't this really apply to pretty much all the programming languages?" Этот ответ утверждает / показывает, что это предположение является недействительным, поэтому нет смысла обращаться к остальным вопросам, основанным на ошибочной предпосылке ,