Полнота по Тьюрингу

Полнота по Тьюрингу — это наличие конструкций ветвления, повтора и данных. Все ингредиенты уже собраны в предыдущих разделах:

  • данные — кодирование Чёрча (раздел «Кодирование Чёрча»): булевы значения, пары, нумералы, списки;
  • ветвление — булево значение Чёрча само выбирает ветку: if\mathrm{if} λbte.bte\equiv \lambda b\,t\,e.\,b\,t\,e;
  • циклы — комбинатор неподвижной точки (раздел «Комбинатор Карри»): YF\mathrm{Y}\,F =βF(YF)=_\beta F\,(\mathrm{Y}\,F), рекурсия без имён и без счётчиков.

Этого хватает, чтобы смоделировать машину Тьюринга: лента — список, состояние — нумерал, шаг — ветвление, повторение шагов — Y\mathrm{Y}. У полноты по Тьюрингу есть цена — неразрешимость. Нет алгоритма, который по терму определяет, есть ли у него нормальная форма: это λ-аналог проблемы останова.

Теперь про тотальность. Язык называется тотальным, если каждая его программа завершается на каждом входе. Полнота по Тьюрингу с тотальностью несовместима. В λ-исчислении возможны бесконечные циклы:

Ω\displaystyle \Omega (λx.xx)(λx.xx)\displaystyle \equiv (\lambda x.x\,x)\,(\lambda x.x\,x) βΩ\displaystyle \to_\beta \Omega βΩ\displaystyle \to_\beta \Omega β\displaystyle \to_\beta \dots

Языки, выбравшие тотальность, существуют — например, Agda. Компилятор Agda проверяет каждую функцию: все случаи входа должны быть разобраны, а рекурсивные вызовы — идти по структурно меньшим аргументам. Сложение натуральных чисел проверку проходит — рекурсивный вызов идёт на nn — строго меньшую часть suc n\mathtt{suc}\ n:

data: Set where
  zero :
  suc  : ℕ → ℕ

_+_ : ℕ → ℕ → ℕ
zero  + m = m
suc n + m = suc (n + m)   -- вызов на n: строго меньше, чем suc n

А вот это Agda отвергнет:

loop : ℕ → ℕ
loop n = loop n           -- Termination checking failed: n не убывает

Плата симметрична: Agda не Тьюринг-полна — Ω\Omega в ней не написать. Зато каждая программа — тотальная функция, и по соответствию Карри — Ховарда (раздел «Соответствие Карри — Ховарда») её можно читать как доказательство. Haskell выбирает середину: типизированное ядро плюс общая рекурсия (fix\mathtt{fix}, рекурсивный let) — полнота возвращается, а с ней и вечные циклы.