next up previous contents
Next: Type inference as equation Up: Introducing types into the Previous: Simply typed lambda calculus   Contents

Polymorphic typed calculi

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 $a$,$b$ .... We assume that our lambda calculus includes constants in addition to variable, so we also permit an arbitrary set of type constants, typically denoted $i$, $j$, ....

The set of type schemes is given by the following grammar:


\begin{displaymath}
s ::= a \mid i \mid s \to s \mid \forall a.s
\end{displaymath}

The terms of the second order polymorphic lambda calculus are then given by

  1. Every variable and constant is a term.

  2. If $M$ and $N$ are terms, so is $(M N)$.

  3. If $M$ is a term, $x$ is a variable and $s$ is a type scheme, then $(\lambda x \in s. M)$ is a term.

  4. If $M$ is a term and $s$ is a type scheme, $(M s)$ is a term.

  5. If $M$ is a term and $a$ is a type variable, then $(\Lambda
a.M)$ is a term.

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


\begin{displaymath}
\Lambda a. \lambda x \in a. x
\end{displaymath}

We have two $\beta $ 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 $A \vdash M:s $ where $A$ is list $\{x_i : t_i\}$ of type ``assumptions'' for variables. We read this relation as follows: under the assumptions in $A$, the expression $M$ has type $s$.

Clearly, for a variable $x$,


\begin{displaymath}
A \vdash x:s \mbox{~iff~} (x : s) \mbox{~belongs to~} A
\end{displaymath}

We use the following notation to extend lists of assumptions. Let $A$ be a list of assumptions. Then, $A + \{x:s\}$ is the list in which all variables other than $x$ carry the same assumptions as in $A$ and any assumption for $x$ in $A$, if such an assumption exists, is overridden by the new assumption $x:s$.

We can now present the type inference rules for complex terms as follows:


\begin{displaymath}
\begin{array}{c}
\displaystyle \frac{A + \{x : s\} \vdash M...
...: \forall a.s}
{A \vdash Mt : s\{a \leftarrow t\}}
\end{array}\end{displaymath}

For instance, we can derive a type for our polymorphic identity function as follows:


\begin{displaymath}
\displaystyle\frac{x:a \vdash x:a}{
\displaystyle\frac{\vd...
...
{\vdash(\Lambda a.\lambda x \in a.x) : \forall a. a
\to a}}
\end{displaymath}

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

map :: (a -> b) -> [a] -> [b]

we really mean the type


\begin{displaymath}
\mbox{map~} :: \forall a,b.~(a \to b) \to [a] \to [b]
\end{displaymath}

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


\begin{displaymath}
\forall a.~[(\forall b.~a \to b) \to a \to a]
\end{displaymath}

Note here that the $\forall b$ quantifier inside the expression cannot be pulled out to the top without altering the meaning of the type.


next up previous contents
Next: Type inference as equation Up: Introducing types into the Previous: Simply typed lambda calculus   Contents
Madhavan Mukund 2004-04-29