The simply typed lambda calculus has only explicit types. It cannot describe polymorphic functions as found, say, in Haskell. To remove this limitation, Girard and Reynolds independently invented what is called the second-order polymorphic lambda calculus (also known as Girard's System F).
We begin by extending the syntax of types to include type variables, that we shall denote , .... We assume that our lambda calculus includes constants in addition to variable, so we also permit an arbitrary set of type constants, typically denoted , , ....
The set of type schemes is given by the following grammar:
The terms of the second order polymorphic lambda calculus are then given by
The last two rules allow us to define functions whose types are defined in terms of type variables (like polymorphic functions in Haskell). Rule 5 says that we can make a type variable a parameter of an expression to get type abstraction, just as we make a normal variable into a parameter in lambda abstraction. Rule 4 permits us to ``apply'' a type abstraction to a concrete type.
In this syntax, a polymorphic identity function would be written as
We have two rules, one for lambda abstraction and one for type abstraction.
It turns out that this calculus is also strongly normalizing. However, it is not known whether the type checking problem is decidable--that is, given a term, can it be assigned a sensible type. This process of assigning a type to a term without explicit type information is called type inference.
What is a ``sensible'' type? There is a natural way in which the type of a complex expression can be deduced from the types assigned to its constituent parts. One way to formalize this is to define a relation where is list of type ``assumptions'' for variables. We read this relation as follows: under the assumptions in , the expression has type .
Clearly, for a variable ,
We use the following notation to extend lists of assumptions. Let be a list of assumptions. Then, is the list in which all variables other than carry the same assumptions as in and any assumption for in , if such an assumption exists, is overridden by the new assumption .
We can now present the type inference rules for complex terms as follows:
For instance, we can derive a type for our polymorphic identity function as follows:
As we remarked earlier, the decidability of type inference for this calculus is open--that is, we do not have an algorithm (nor can we prove the nonexistence of an algorithm) to decide whether a given expression can be assigned a type that is consistent with the inference system described above.
How is it then that type checking is possible for languages like Haskell? The answer is that Haskell, ML and other functional programming languages use a restricted version of the polymorphic types defined here.
In Haskell, all type variable are universally quantified at the top level. Thus, if we write a Haskell type such as
we really mean the type
This implicit top level universal quantification of all type variables is known as shallow typing. In contrast, the second order polymorphic calculus, we can permits deep typing, with more complicated type expressions such as
Note here that the quantifier inside the expression cannot be pulled out to the top without altering the meaning of the type.