Typed lambda calculi

In the λ-calculus paradigm, types are a guarantee that a program finishes in finite time. The price is the loss of Turing completeness; the payoff is predictability, documentation and, as we have already seen, an entire logic.

The simplest such system is the simply typed λ-calculus (λ\lambda^{\to}). Every term in it is assigned a type, and the types themselves are built from atomic α\alpha by a single construction — the arrow:

τ  ::=  α    τ\displaystyle \tau \;\mathrel{\text{::=}}\; \alpha \;\mid\; \tau τ\displaystyle \to \tau
(1.37)

Three inference rules — one per term construct:

x:τΓΓx:τΓ,  x:σM:τΓλx.M:στΓM:στΓN:σΓMN:τ\frac{x{:}\tau \in \Gamma}{\Gamma \vdash x : \tau} \qquad \frac{\Gamma,\; x{:}\sigma \vdash M : \tau}{\Gamma \vdash \lambda x.\,M : \sigma \to \tau} \qquad \frac{\Gamma \vdash M : \sigma \to \tau \quad \Gamma \vdash N : \sigma}{\Gamma \vdash M\,N : \tau}
(1.38)

Left to right: a variable takes its type from the context Γ\Gamma; abstraction — if under the assumption x:σx{:}\sigma the body MM has type τ\tau, then λx.M\lambda x.\,M is a function of type σ\sigma τ\to\tau; application — a function of type σ\sigma τ\to\tau may only be applied to an argument of type σ\sigma, and the result gets type τ\tau.

There are two important properties: the first is strong normalization: every typable term has a normal form, and every reduction order reaches it — as a consequence, Ω\Omega and Y\mathrm{Y} are untypable and the language is not Turing-complete; the second is subject reduction: the type is preserved under reduction — “computation does not spoil the type”.

What about polymorphism? Polymorphism in types, that is. λ\lambda^{\to} has none: the identity function λx:α.x\lambda x{:}\alpha.\,x is tied to one particular type α\alpha, and it has to be written anew for every type. Girard and Reynolds removed this restriction by allowing terms to abstract not only over values but also over types themselves — the result is System F, where the following holds:

id\displaystyle \mathrm{id} =Λα.λx:α.x  :  α.α\displaystyle = \Lambda\alpha.\,\lambda x{:}\alpha.\,x \;:\; \forall\alpha.\,\alpha α\displaystyle \to\alpha

Here Λα\Lambda\alpha is abstraction over a type: id\mathrm{id} first takes a type α\alpha, then a value xx of that type, and returns it unchanged. The type α.α\forall\alpha.\,\alpha α\to\alpha reads “for any type α\alpha, a function from α\alpha to α\alpha”: one and the same id\mathrm{id} applies to a number, a boolean, a function — polymorphism became part of the calculus itself.

Type inference in full System F is undecidable, so Haskell uses a decidable fragment — the Hindley–Milner system (algorithm W), where all types are inferred without annotations; it is the subject of the next section.