Соответствие Карри — Ховарда

Вот мы и добрались до самого важного. Программа на языке, взявшем за основу λ-исчисление и решившем проблему зацикливания, — это теорема: её формулировка — тип, компиляция — проверка доказательства, а исполнение гарантированно возвращает результат обещанного типа. С императивными языками такое тоже возможно — корректность программ доказывают со времён логики Хоара, — но куда сложнее: там доказательство не сама программа, а отдельная надстройка из предусловий, постусловий и инвариантов.

Итак, программы, написанные на языках, базирующихся на λ-исчислении, проще доказывать, чем программы на других языках — не обязательно императивных, скажем, на Python. В этой главе мы рассмотрим соответствие Карри — Ховарда, мост между логикой и программированием: написать программу типа τ\tau — то же самое, что доказать высказывание τ\tau. Заметил его Карри ещё в 1930-х — по типам комбинаторов, — а точную форму оно получило в рукописи Ховарда 1969 года.

Присмотримся к типам знакомых термов, читая стрелку как импликацию «из AA следует BB». Тождественная функция оказывается доказательством тавтологии «из AA следует AA»:

λx.x  :  A\displaystyle \lambda x.\,x \;:\; A A\displaystyle \to A

Комбинатор K\mathrm{K}, отбрасывающий второй аргумент, — доказательство высказывания «если верно AA, то AA следует из чего угодно»:

λxy.x  :  A\displaystyle \lambda x\,y.\,x \;:\; A (BA)\displaystyle \to (B \to A)

Комбинатор S\mathrm{S} тоже доказывает теорему — про раздачу посылки двум следствиям:

λxyz.xz(yz)  :  (ABC)\displaystyle \lambda x\,y\,z.\,x\,z\,(y\,z) \;:\; (A \to B \to C) (AB)\displaystyle \to (A \to B) (AC)\displaystyle \to (A \to C)

Приведём, пожалуй, таблицу соответствия:

ЛогикаТипы / программы
импликация AA B\Rightarrow Bтип функции AA B\to B
конъюнкция ABA \wedge Bпара (A,B)(A,\,B)
дизъюнкция ABA \vee BEither  A  B\mathtt{Either}\;A\;B
истина \topединичный тип ()()
ложь \botпустой тип Void\mathtt{Void}
доказательствотерм (программа)
упрощение доказательстваβ-редукция
\forall второго порядкаполиморфизм System F
,  \forall,\;\exists над объектамизависимые типы (LiquidHaskell)

Немного пояснений. Единичный тип ()() — тип ровно с одним значением. Построить его значение можно всегда и тривиально, поэтому   \top \;   ()\leftrightarrow\; (). А вот пустой тип Void\mathtt{Void} — тип без единого значения: программу такого типа написать невозможно. Это и есть ложь: у \bot нет доказательств.

«\forall второго порядка → полиморфизм System F» (мы это ещё обсудим дальше). Квантор второго порядка пробегает по самим высказываниям (в мире типов — по типам): «для любого высказывания AA верно …». В программах это в точности параметрический полиморфизм: тип тождественной функции a.  a\forall a.\; a a\to a читается как теорема «для любого AA: из AA следует AA». System F — это λ-исчисление, где такая квантификация по типам встроена в язык; на нём основан полиморфизм Haskell.

«,  \forall,\;\exists над объектами → зависимые типы». Кванторы первого порядка пробегают уже не по высказываниям, а по объектам — конкретным значениям: «для любого числа nn …», «существует список xx, такой что …». Чтобы записать такое типом, тип должен зависеть от значения — это и есть зависимые типы. В самом Haskell \forall есть только по типам, зато LiquidHaskell навешивает на типы логические предикаты — уточняющие типы: сигнатура head:{v:[a]len  v\mathtt{head'} : \{\,v : [a] \mid \mathtt{len}\;v >0}> 0\,\} a\to a читается как «для любого списка vv, длина которого больше нуля …», а импликации между предикатами проверяет SMT-решатель. Квантор \exists выражается уточнением результата: {v:Inteven  v}\{\,v : \mathtt{Int} \mid \mathtt{even}\;v\,\} — предъявленное чётное число вместе с гарантией чётности. Подробнее — в разделе «LiquidHaskell».

Мы возвращаемся к проблеме полноты по Тьюрингу. Неограниченная рекурсия fix:(AA)\mathtt{fix} : (A \to A) A\to A в логическом прочтении — «если из AA следует AA, то AA верно»: порочный круг, доказывающий что угодно. Поэтому языки-доказатели обязаны быть тотальными: проверка тотальности из прошлого раздела — не перестраховка, а условие честности логики. На этом соответствии стоит пруф-ассистент Agda: спецификация записывается типом, программа этого типа и есть доказательство, а проверка доказательства — обычная проверка типов.