Fall 98, CSE 520: Lecture 9 (Sep 24)

The type system of Curry

The syntax of the types and the rules of the system are those given in Lecture 3 (See the corresponding Lecture Notes). In this lecture we will see some properties of the system and various examples.

In the following, the statement "M  has type  A" (or equivalently, "A  is a type of  M"), notation  |- M : A  or simply  M : A, stand 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 
We can also prove that  \x.x  has 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 first type (A -> A) seems a "better answer". 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 = { B |-> A , C |-> A -> A }.  theta, applied to  C, gives the type  A -> A. (Alternatively we could have considered the substitution  { A |-> B , C |-> B -> B }, 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 a typing of this term, we end up with the condition that the type  A  of  x  should be equal to a type of the form  A -> B, which is clearly impossible for any finite type expression.

Intuitively, it was to be expected that  \x.x x  has no type. In fact,  x x  represents the application of a generic function  x  to itself. Now, 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.

For the same reason, also the fixpoint operator  Y  (see notes of Lecture 2) has no type.

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). We will use the notation  x |-> theta(x).

The application of a substitution  theta  to a term  t, denoted by  t theta, is the term obtained from  t  by replacing 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  ti theta  is identical to  ui theta. 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.