Вот мы и добрались до самого важного. Программа на языке, взявшем за основу λ-исчисление и решившем проблему зацикливания, — это теорема: её формулировка — тип, компиляция — проверка доказательства, а исполнение гарантированно возвращает результат обещанного типа. С императивными языками такое тоже возможно — корректность программ доказывают со времён логики Хоара, — но куда сложнее: там доказательство не сама программа, а отдельная надстройка из предусловий, постусловий и инвариантов.
Итак, программы, написанные на языках, базирующихся на λ-исчислении, проще доказывать, чем программы на других языках — не обязательно императивных, скажем, на Python. В этой главе мы рассмотрим соответствие Карри — Ховарда, мост между логикой и программированием: написать программу типа — то же самое, что доказать высказывание . Заметил его Карри ещё в 1930-х — по типам комбинаторов, — а точную форму оно получило в рукописи Ховарда 1969 года.
Присмотримся к типам знакомых термов, читая стрелку как импликацию «из следует ». Тождественная функция оказывается доказательством тавтологии «из следует »:
Комбинатор , отбрасывающий второй аргумент, — доказательство высказывания «если верно , то следует из чего угодно»:
Комбинатор тоже доказывает теорему — про раздачу посылки двум следствиям:
Приведём, пожалуй, таблицу соответствия:
| Логика | Типы / программы |
|---|---|
| импликация | тип функции |
| конъюнкция | пара |
| дизъюнкция | |
| истина | единичный тип |
| ложь | пустой тип |
| доказательство | терм (программа) |
| упрощение доказательства | β-редукция |
| второго порядка | полиморфизм System F |
| над объектами | зависимые типы (LiquidHaskell) |
Немного пояснений. Единичный тип — тип ровно с одним значением. Построить его значение можно всегда и тривиально, поэтому . А вот пустой тип — тип без единого значения: программу такого типа написать невозможно. Это и есть ложь: у нет доказательств.
« второго порядка → полиморфизм System F» (мы это ещё обсудим дальше). Квантор второго порядка пробегает по самим высказываниям (в мире типов — по типам): «для любого высказывания верно …». В программах это в точности параметрический полиморфизм: тип тождественной функции читается как теорема «для любого : из следует ». System F — это λ-исчисление, где такая квантификация по типам встроена в язык; на нём основан полиморфизм Haskell.
« над объектами → зависимые типы». Кванторы первого порядка пробегают уже не по высказываниям, а по объектам — конкретным значениям: «для любого числа …», «существует список , такой что …». Чтобы записать такое типом, тип должен зависеть от значения — это и есть зависимые типы. В самом Haskell есть только по типам, зато LiquidHaskell навешивает на типы логические предикаты — уточняющие типы: сигнатура читается как «для любого списка , длина которого больше нуля …», а импликации между предикатами проверяет SMT-решатель. Квантор выражается уточнением результата: — предъявленное чётное число вместе с гарантией чётности. Подробнее — в разделе «LiquidHaskell».
Мы возвращаемся к проблеме полноты по Тьюрингу. Неограниченная рекурсия в логическом прочтении — «если из следует , то верно»: порочный круг, доказывающий что угодно. Поэтому языки-доказатели обязаны быть тотальными: проверка тотальности из прошлого раздела — не перестраховка, а условие честности логики. На этом соответствии стоит пруф-ассистент Agda: спецификация записывается типом, программа этого типа и есть доказательство, а проверка доказательства — обычная проверка типов.