Turing completeness

Turing completeness is the presence of branching, repetition and data. All the ingredients have already been assembled in the previous sections:

  • data — Church encoding (section “Church encoding”): booleans, pairs, numerals, lists;
  • branching — a Church boolean picks the branch itself: if\mathrm{if} λbte.bte\equiv \lambda b\,t\,e.\,b\,t\,e;
  • loops — the fixed-point combinator (section “Curry's Y combinator”): YF\mathrm{Y}\,F =βF(YF)=_\beta F\,(\mathrm{Y}\,F), recursion without names and without counters.

This is enough to model a Turing machine: the tape is a list, the state is a numeral, a step is branching, repeating the steps is Y\mathrm{Y}. Turing completeness comes at a price — undecidability. There is no algorithm that, given a term, decides whether it has a normal form: this is the λ-counterpart of the halting problem.

Now, totality. A language is called total if every one of its programs terminates on every input. Turing completeness is incompatible with totality. In the λ-calculus infinite loops are possible:

Ω\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

Languages that chose totality do exist — for example, Agda. The Agda compiler checks every function: all input cases must be covered, and recursive calls must go to structurally smaller arguments. Addition of natural numbers passes the check — the recursive call is on nn — a strictly smaller part of suc n\mathtt{suc}\ n:

data: Set where
  zero :
  suc  : ℕ → ℕ

_+_ : ℕ → ℕ → ℕ
zero  + m = m
suc n + m = suc (n + m)   -- the call is on n: strictly smaller than suc n

And this one Agda will reject:

loop : ℕ → ℕ
loop n = loop n           -- Termination checking failed: n does not decrease

The price is symmetric: Agda is not Turing complete — Ω\Omega cannot be written in it. In return, every program is a total function, and by the Curry–Howard correspondence (section “The Curry–Howard correspondence”) it can be read as a proof. Haskell picks the middle ground: a typed core plus general recursion (fix\mathtt{fix}, recursive let) — completeness returns, and with it infinite loops.