TypecheckingA Typechecker for STLC

The has_type relation of the STLC defines what it means for a term to belong to a type (in some context). But it doesn't, by itself, give us an algorithm for checking whether or not a term is well typed.
Fortunately, the rules defining has_type are syntax directed — that is, for every syntactic form of the language, there is just one rule that can be used to give a type to terms of that form. This makes it straightforward to translate the typing rules into clauses of a typechecking function that takes a term and a context and either returns the term's type or else signals that the term is not typable.

Comparing Types

First, we need a function to compare two types for equality...
Fixpoint eqb_ty (T1 T2:ty) : bool :=
  match T1,T2 with
  | Bool, Bool
      true
  | Arrow T11 T12, Arrow T21 T22
      andb (eqb_ty T11 T21) (eqb_ty T12 T22)
  | _,_ ⇒
      false
  end.
... and we need to establish the usual two-way connection between the boolean result returned by eqb_ty and the logical proposition that its inputs are equal.
Lemma eqb_ty_refl : T1,
  eqb_ty T1 T1 = true.

Lemma eqb_ty__eq : T1 T2,
  eqb_ty T1 T2 = trueT1 = T2.

The Typechecker

The typechecker works by walking over the structure of the given term, returning either Some T or None. Each time we make a recursive call to find out the types of the subterms, we need to pattern-match on the results to make sure that they are not None. Also, in the app case, we use pattern matching to extract the left- and right-hand sides of the function's arrow type (and fail if the type of the function is not Arrow T11 T12 for some T11 and T12).

Fixpoint type_check (Gamma : context) (t : tm) : option ty :=
  match t with
  | var x
      Gamma x
  | abs x T11 t12
      match type_check (update Gamma x T11) t12 with
      | Some T12Some (Arrow T11 T12)
      | _None
      end
  | app t1 t2
      match type_check Gamma t1, type_check Gamma t2 with
      | Some (Arrow T11 T12),Some T2
          if eqb_ty T11 T2 then Some T12 else None
      | _,_ ⇒ None
      end
  | tru
      Some Bool
  | fls
      Some Bool
  | test guard t f
      match type_check Gamma guard with
      | Some Bool
          match type_check Gamma t, type_check Gamma f with
          | Some T1, Some T2
              if eqb_ty T1 T2 then Some T1 else None
          | _,_ ⇒ None
          end
      | _None
      end
  end.

Digression: Improving the Notation

Before we consider the properties of this algorithm, let's write it out again in a cleaner way, using "monadic" notations in the style of Haskell to streamline the plumbing of options. First, we define a notation for composing two potentially failing (i.e., option-returning) computations:
Notation " x <- e1 ;; e2" := (match e1 with
                              | Some xe2
                              | NoneNone
                              end)
         (right associativity, at level 60).
Second, we define return and fail as synonyms for Some and None:
Notation " 'return' e "
  := (Some e) (at level 60).

Notation " 'fail' "
  := None.

Now we can write the same type-checking function in a more imperative-looking style using these notations.
Fixpoint type_check (Gamma : context) (t : tm) : option ty :=
  match t with
  | var x
      match Gamma x with
      | Some Treturn T
      | Nonefail
      end
  | abs x T11 t12
      T12 <- type_check (update Gamma x T11) t12 ;;
      return (Arrow T11 T12)
  | app t1 t2
      T1 <- type_check Gamma t1 ;;
      T2 <- type_check Gamma t2 ;;
      match T1 with
      | Arrow T11 T12
          if eqb_ty T11 T2 then return T12 else fail
      | _fail
      end
  | tru
      return Bool
  | fls
      return Bool
  | test guard t1 t2
      Tguard <- type_check Gamma guard ;;
      T1 <- type_check Gamma t1 ;;
      T2 <- type_check Gamma t2 ;;
      match Tguard with
      | Bool
          if eqb_ty T1 T2 then return T1 else fail
      | _fail
      end
  end.

Properties

To verify that the typechecking algorithm is correct, we show that it is sound and complete for the original has_type relation — that is, type_check and has_type define the same partial function.
Theorem type_checking_sound : Gamma t T,
  type_check Gamma t = Some Thas_type Gamma t T.

Theorem type_checking_complete : Gamma t T,
  has_type Gamma t Ttype_check Gamma t = Some T.