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

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

Итак, программы, написанные на языках, базирующихся на λ-исчислении, проще доказывать, чем программы на других языках — не обязательно императивных, скажем, на 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 следует из чего угодно»:

λx y. x  :  A\displaystyle \lambda x\,y.\,x \;:\; A →(B→A)\displaystyle {} \to (B \to A)

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

λx y z. x z (y z)  :  (A→B→C)\displaystyle \lambda x\,y\,z.\,x\,z\,(y\,z) \;:\; (A \to B \to C) →(A→B)\displaystyle {} \to (A \to B)​→(A→C)\displaystyle {} \to (A \to C)

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

ЛогикаТипы / программы
импликация AA​⇒B{} \Rightarrow Bтип функции AA​→B{} \to B
конъюнкция A∧BA \wedge Bпара (A, B)(A,\,B)
дизъюнкция A∨BA \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:Int∣even  v }\{\,v : \mathtt{Int} \mid \mathtt{even}\;v\,\} — предъявленное чётное число вместе с гарантией чётности. Подробнее — в главе «LiquidHaskell».

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