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 (). Every term in it is assigned a type, and the types themselves are built from atomic by a single construction — the arrow:
Three inference rules — one per term construct:
Left to right: a variable takes its type from the context ; abstraction — if under the assumption the body has type , then is a function of type ; application — a function of type may only be applied to an argument of type , and the result gets type .
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, and 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. has none: the identity function is tied to one particular type , 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:
Here is abstraction over a type: first takes a type , then a value of that type, and returns it unchanged. The type reads “for any type , a function from to ”: one and the same 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.