# A Type Checker for SimPL

Let's implement a type checker for SimPL, based on the type system we defined in the previous section.

## Types

We need a variant to represent types:

type typ =
| TInt
| TBool


The natural name for that variant would of course have been "type" not "typ", but the former is already a keyword in OCaml. We have to prefix the constructors with "T" to disambiguate them from the constructors of the expr type, which include Int and Bool.

## Contexts

Let's introduce a small signature for typing contexts, based on the abstractions we've introduced so far: the empty context, looking up a variable, and extending a context.

module type Context = sig
(** [t] is the type of a context. *)
type t

(** [empty] is the empty context. *)
val empty : t

(** [lookup ctx x] gets the binding of [x] in [ctx].
Raises: [Failure] if [x] is not bound in [ctx]. *)
val lookup : t -> string -> typ

(** [extend ctx x ty] is [ctx] extended with a binding
of [x] to [ty]. *)
val extend : t -> string -> typ -> t
end


It's easy to implement that signature with an association list.

module Context : Context = struct
type t = (string * typ) list

let empty = []

let lookup ctx x =
try List.assoc x ctx
with Not_found -> failwith "Unbound variable"

let extend ctx x ty =
(x, ty) :: ctx
end


## The Typing Relation

Now we can implement the typing relation |-. We'll do that by writing a function typeof : Context.t -> expr -> typ, such that typeof ctx e = t if and only if ctx |- e : t. Note that the typeof function produces the type as output, so the function is actually inferring the type! That inference is easy for SimPL; it would be considerably harder for larger languages.

open Context

(** [typeof ctx e] is the type of [e] in context [ctx].
Raises: [Failure] if [e] is not well typed in [ctx]. *)
let rec typeof ctx = function
| Int _ -> TInt
| Bool _ -> TBool
| Var x -> lookup ctx x
...


Note how the implementation of typeof so far is based on the rules we previously defined for |-. In particular:

• typeof is a recursive function, just as |- is an inductive relation.
• The base cases for the recursion of typeof are the same as the base cases for |-.

Also note how the implementation of typeof differs in one major way from the definition of |-: error handling. The type system didn't say what to do about errors; rather, it just defined what it meant to be well typed. The type checker, on the other hand, needs to take action and report ill typed programs. Our typeof function does that by raising exceptions. The lookup function, in particular, will raise an exception if we attempt to lookup a variable that hasn't been bound in the context.

Let's continue with the recursive cases:

  ...
| Let (x, e1, e2) -> typeof_let ctx x e1 e2
| Binop (bop, e1, e2) -> typeof_bop ctx bop e1 e2
| If (e1, e2, e3) -> typeof_if ctx e1 e2 e3


We're factoring out a helper function for each branch for the sake of keeping the pattern match readable. Each of the helpers directly encodes the ideas of the |- rules, with error handling added.

and typeof_let ctx x e1 e2 =
let t1 = typeof ctx e1 in
let ctx' = extend ctx x t1 in
typeof ctx' e2

and typeof_bop ctx bop e1 e2 =
let t1, t2 = typeof ctx e1, typeof ctx e2 in
match bop, t1, t2 with
| Mult, TInt, TInt -> TInt
| Leq, TInt, TInt -> TBool
| _ -> failwith "Operator and operand type mismatch"

and typeof_if ctx e1 e2 e3 =
if typeof ctx e1 = TBool
then begin
let t2 = typeof ctx e2 in
if t2 = typeof ctx e3 then t2
else failwith "Branches of if must have same type"
end
else failwith "Guard of if must have type bool"


Note how the recursive calls in the implementation of typeof occur exactly in the same places where the definition of |- is inductive.

## Type Checking

Finally, we can implement a function to check whether an expression is well typed:

(** [typecheck e] checks whether [e] is well typed in
the empty context. Raises: [Failure] if not. *)
let typecheck e =
ignore (typeof empty e)