Fall 2000, CSE 520: Lectures 8, 9, 10, 11 and 12

The Typed Lambda Calculus

The typed lambda calculus provides foundations for the concept of type, which is used in all modern high-level programming languages. Some of the advantages of having a notion of type are;
  1. Readability
  2. Static correctness
  3. Efficiency of implementation
  4. Modularity (interfaces of modules, inheritance,...)

There are two main type systems for the simply typed Lambda Calculus: The system of Curry (1934) and the system of Church (1940).

The system of Curry is the basis of typed languages like ML, where variables do not require explicit type annotation. The type system of Church is the basis of languages like Pascal and C, where the type of every variable must be declared.

The Type System of Curry

Hystorical note: Haskell Curry lived in State College and worked at the Mathematical Department of Penn State! See the dedication to him at the entrance of the Math building.

In this system, the syntax of Lambda terms is the same as in the untyped version.

The types are defined by the following grammar:

Type ::= TVar           (type variable)
       | (Type -> Type)   (function)

Convention: In order to eliminate some parentheses, we assume -> to be right associative. So (A1 -> (A2 -> (A3 -> ... (An-1 -> An) ... ))) will be written simply as A1 -> A2 -> A3 -> ... An-1 -> An.

The system allows to derive statements of the following form:

G |- M : A
meaning: under the assumptions G (asociations between lambda variables and types), the lambda term M has type A.

The rules for the Type System are the following:

     (var)  ---------------   if x : A is in G 
              G |- x : A 

              G |- M : A -> B    G |- N : A 
     (app)  ---------------------------------- 
                    G |- M N : B 

               G , x : A |- M : B 
     (abs)  ------------------------ 
              G |- \x. M : A -> B 

In the following, the statement "M  has type  A" (or equivalently, "A  is a type of  M"), notation  |- M : A  or simply  M : A, stands for "the statement  {} |- M : A  has a proof in the type system of Curry". Note that the set of assumption must be empty.


Consider the lambda term  \x.x. Does it have a type? The answer is yes, in fact we can prove that  \x.x  has type  A -> A  for any type variable  A. The proof is the following:
   (var) ----------------  
          x : A |- x : A
   (abs) ------------------
          |- \x.x : A -> A 

Some questions

There are various questions that one may ask at this point. Here we list some of them:
  1. Does a term always has a type?
  2. If a term has a type, is this type unique?
  3. If the type is not unique, is there a sort of "most natuaral type" that one can choose?
  4. We have seen how to use the system so to prove that a given term has a given type (type checking). Is it possible also to use the system to infer a type for a term (type inference)?
  5. Given a type T, does it always exists a term which has type T?
  6. What is the relation between types and reduction? Namely, is the type of a term preserved under reduction?
In the rest of these notes, we will discuss these questions.


In the pure lambda calculus, if a term M has a type T, then it has infinitely many types. Essentially, all the types that we can obtain from T by replacing the type variables with type expressions, are also types of M.


Consider again the lambda term  \x.x. We can prove that  \x.x  has also type  (B -> B) -> (B -> B)  for any type variable  B. In fact, by replacing  A  by  B -> B  in the proof above, we obtain the following:
      (var) --------------------------
             x : B -> B |- x : B -> B 
   (abs) --------------------------------
          |- \x.x : (B -> B) -> (B -> B) 
In a sense, however, the previous type (A -> A) seems a "better type". In fact, the type  (B -> B) -> (B -> B)  can be seen as a particular case of  A -> A, but not viceversa. In other words,  A -> A  is more general  than  (B -> B) -> (B -> B). We can indeed prove that  A -> A  is the most general (or principal) type of  \x.x.

Principal type

A type  A  is a principal (or most general) type of  M  if every other type of  M  can be derived from  A  by instantiation.

Theorem If a lambda term has a type, then it has a principal type, unique modulo renaming.

We don't give a formal proof of this theorem, but we show how to construct the principal type. Let us start with the example of the term  \x.x. Intuitively, any proof of a type statement for  \x.x  must have the following form:

   (var) ---------------- A = B
          x : A |- x : B
   (abs) ---------------- C = A -> B 
          |- \x.x : C
The equation  C = A -> B  indicates the condition under which (abs) is applicable, and the equation  A = B  indicates the condition under which (var) is applicable. Hence  A = B , C = A -> B  are the conditions under which the proof is valid. Any solution of these equations (i.e. any substitution which makes  A  and  B  identical, and  C  and  A -> B  identical), applied to the type in the conclusion (i.e.  C), will derive a type for  \x.x. Clearly, the most general type is obtained by taking the most general solution of the equations (see section on unification below). One form of the most general solution is the substitution  theta = [A/B, A->A/C].  theta, applied to  C, gives the type  A -> A. (Alternatively we could have considered the substitution  [B/A , B->B/C], that would have given the type  B -> B, which is equivalent to  A -> A  modulo renaming.)

Constructing the principal type

To construct the principal type of a term  M, try to build a proof for the statement  |- M : A, with  A  generic. If the (generic) proof can be built, then collect all the conditions (equations). If these equations are solvable, consider their most general solution  theta. We have that
the principal type of   M   is   A theta.
If, on the contray, the generic proof cannot be built, or the equations are not solvable, then  M  is not typeable.


Type checking and type inference

As we have seen in the above examples, the Curry system can be used in two ways:

Applications: ML

The programming language ML has a static analyser that performs type inference (and derives the principal type) on the basis of the Curry's system. For instance, if we write the declaration
- val f = fn x => x;
(remember that  fn x => E  is the ML syntax for  \x.E), the ML answer is:
val f = fn : 'a -> 'a
(where  'a  represents a type variable), meaning that  f  is a function of type  'a -> 'a.

Lambda terms without a type

Not all lambda terms have a type; for instance,  \x.x x  hasn't any. In fact, if we try to construct a proof for typing a term containing  x x, we end up with the condition that the type  A  of  x  (occurring as the argument) should be equal to a type of the form  A -> B (type of a function which takes  x  as an argument). This is clearly impossible for any finite type expression. Remeber that we are considering the type expressions generated by the grammar at the beginning of these notes, and that the expressions (strings) generated by a grammar are always finite. For the same reason, also the fixpoint operator  Y  has no type.

Note that  x x  represents the application of a generic function  x  to itself. There are functions for which it makes sense to be applied to themselves (for instance the identity function), but this is not the case for all functions.

From a semantic point of view, note that if  A -> B  is to be interpreted as the set of functions from a set  A  to a set  B, then there is no non-trivial set  A  which can be equal to the set  A -> B, for cardinality reasons. In fact, if  A  has cardinality n, and  B  has cardinality m, then  A -> B has cardinality mn. In domain theory, where it is desirable that such equations have a solution,  A -> B  is assumed to represent not all functions from  A  to  B, but only a particular class of them.

Empty and inhabited types

We have seen that there are lambda terms without a type. Analogously, there are type expressions which do not represent the type of any lambda term. We say that they are "empty types" or "types which are not inhabited". Examples of such types are: Both the inhabited and the empty types are infinitely many. We will see in next lectures that there is a nice characterization of the inhabited types:
the inhabited types are exactly the formulas which are valid in the intuitionistic propositional logic
where  ->  is to be interpreted as logical implication.

First order unification

We have seen that a type inference might require solving a set of equations between type expressions. We make now more precise what we mean by "solution" and by "most general solution". We give the general definition for equations between first-order terms, of which type expressions represent a particular case.

Definition Given a set of variables Var, and a set of function symbols Fun (possibly including constant symbols) the first-order terms are defined by the following grammar:

Term ::= Var | Fun(Term,...,Term)

In the case of type expressions, we have only one binary function symbol (represented in infix notation): the arrow ->.

Definition A substitution theta is any mapping theta : Var -> Term.

A substitution theta will be denoted by listing explicitly the result of its application to every variable (usually we are interested only in finite substitutions, i.e. substitutions which affect only a finite number of variables). More precisely, a substitution theta such that theta(x1) = t1,..., theta(xn) = tn, will be denoted by [t1/x1,..., tn/xn].

The application of a substitution theta to a term t, denoted by t theta, is the term obtained from t by replacing symultaneously each variable x by theta(x).

The composition of two substitutions sigma and theta is the substitution sigma theta s.t. for every variable x, (sigma theta)(x) = (x sigma)theta.

Definition Given a set of equations on terms   E = {t1 = u1, ... , tn = un}, a substitution theta is a unifier (solution) for E iff for each i we have that titheta is identical to uitheta. A substitution theta is the most general unifier of E if it is a unifier for E and, for any other unifier sigma, there exists sigma' s.t. sigma = theta sigma' (i.e. sigma can be obtained by instantiating theta).

Example Consider the set of equations

E = {A = B->C , B->C = C->B}
We have that We can actually show that theta is the most general unifier for E.

There are various algoriths to find the most general unifier for a set of first order equations; for instance, the algorithm of Martelli-Montanari (see [Apt_Pellegrini, Section 2]. Note that in this reference they use a reversed notation for subsitution: x/t insteand of t/x).

Proposition The algorithm of Martelli-Montanari always terminates and it gives a most general unifier if the set of equations is solvable, failure otherwise.

Corollary Given a set of first order equations E, it is decidable whether E is solvable or not, and, if it is solvable, then it has a most general unifier (which is unique modulo renaming).

Uses of unification in programming languages

Properties of the type system of Curry

From the properties of FO unification, we have that:

Proposition For any term M, it is decidable whether M is typable or not, and in case it is, then it has a principal type.

This proposition is very important because it states that type inference/checking can be done effectively and without risk of looping.

Another important property is the following, which states that type inference/checking can be done once and for all at compile time, in the sense that if a program typechecks correctly then there is no risk of getting type errors at execution time.

Theorem (Subject reduction) If M : A and M ->> M' then M' : A.

Note that the converse (subject expansion) of this theorem does not hold. Namely, there are terms M and M' such that M ->> M' and M': A, but M is not typeable. Take for instance M = (\y x. x) Y and M' = \x. x. Note that the reason why subject expansion does not hold is related to lazy evaluation.

The system of Church and the Simply Typed Lambda Calculus

The system of Church is very similar to the system of Curry, the only difference is that each variable is explicitly typed (in the term) at the moment in which it is introduced. This philosophy (declare the type of every variable) is adopted in several programming languages, for instance C, C++, Java, Pascal, lambda Prolog, ... On the contrary, ML adopts the more liberal approach of the system of Curry.

The language of Simply Typed lambda terms is defined by the following grammar:

   Term ::= Var | (\Var : Type . Term) | (Term Term)
Type expressions are defined as usual:
   Type ::= TVar | (Type -> Type)
The rules are identical to those of Curry, the only difference is the abs rule, which is forced to introduce the argument type specified in the abstraction variable:
            G , x : A |- M : B
   (abs) -------------------------
          G |- (\x:A. M) : A -> B

Properties of the Simply Typed Lambda Calculus

The important properties of the Lambda Calculus and of the Curry system hold also for the Simply Typed Lambda Calculus and the Church system. In particular the following properties hold: Furthermore, in this system the type of a term is unique:


Relation between the systems of Curry and of Church

Let M be a simply-typed lambda term and let |M| denote the lambda term obtained from M by removing the type decorations. Then we have that M is Church-typeable if and only |M| is Curry-typeable. More precisely:


  1. If M : A in Church, then |M| : A in Curry
  2. If |M| : A in Curry, and A is its principal type, then M : B in Church d for some instance B of A.
  3. If |M| : A in Curry, then there exists N such that N : A in Church and |M|=|N|.
Note that, as a consequence of this proposition, we have that a type is inhabited in Curry iff it is inhabited in Church.