This tutorial serves as a very short and quick summary of the first few chapters of TAPL.
My previous post provided an implementation for the untyped lambda calculus.
This post is focused on building the typed lambda calculus – lambda calculus with a type-checker.
3. Typed lambda calculus
For evaluation, the typed lambda calculus will use the same functions as the untyped, with the only change that every TmAbs will carry a type along. So we have:
data Term =
...
| TmAbs VarName Type Term
...
The corresponding functions (eval, subst, freeVars) need to have the pattern matching clauses updated, and type passed in context where applicable.
3.1. Syntax
Per BNF, the syntax is:
<type> ::= TyVar | TyArr <type> <type>
In Haskell code:
data Type = TyVar | TyArr Type Type deriving (Eq, Show)
That is, well-formed terms in the lambda calculus are either variables, or functions.
3.2. Inference rules
The type rules for E-AppAbs and the syntax for abstraction (as we have seen) is changed in that it introduced the type information.
Here are the typing rules that we will now implement:
| Name | Rule |
| T-Var | |
| T-Abs | |
| T-App |
We will re-use the environment code from the first tutorial, namely TyEnv, addType, and getTypeFromEnv. The Haskell implementation of the rules in the table above:
typeOf :: TyEnv -> Term -> Either String Type
typeOf env (TmVar n) =
case getTypeFromEnv env n of
Just t -> Right t
_ -> error $ "No var found in env: " ++ show n
typeOf env (TmAbs n t1 te) =
let newEnv = addType n t1 env
t2 = typeOf newEnv te in
case t2 of
Right t2 -> Right $ TyArr t1 t2
_ -> Left "Unsupported type for TmAbs"
typeOf env (TmApp t1 t2) =
let t1' = typeOf env t1
t2' = typeOf env t2 in
case t1' of
Right (TyArr a b) | Right a == t2' -> Right b
Right (TyArr a _) -> Left $ "Type mismatch between " ++ show t1' ++ " and " ++ show t2'
_ -> Left "Unsupported type for TmApp"
Playing around with it a bit:
Main> typeEnv = addType "x" TyVar [] Main> typeOf typeEnv (TmVar "x") Right TyVar Main> let lcid = TmAbs "x" TyVar (TmVar "x") in typeOf typeEnv lcid Right (TyArr TyVar TyVar) Main> let lcid = TmAbs "x" TyVar (TmVar "x") in typeOf typeEnv (TmApp lcid (TmVar "x")) Right TyVar
However, the function lcid only accepts a single variable and returns it. It will not type-check if we pass an abstraction to it. Thus, there are infinite id‘s that we must define in the simply typed lambda calculus: ,
, etc. This problem can be fixed by implementing the polymorphic lambda calculus.
3.3. Bonus: Recursion
Given the standard semantics, the simply typed lambda calculus is strongly normalizing: that is, well-typed terms always reduce to a value.
This is because recursion is not allowed by the typing rules: it is impossible to find types for fixed-point combinators and the looping term .
Recursion can be added to the language by either having a special operator of type
or adding general recursive types, though both eliminate strong normalization.
Since it is strongly normalising, it is decidable whether or not a simply typed lambda calculus program halts: in fact, it always halts. We can therefore conclude that the language is not Turing complete.
3.4. Conclusion
Now that we have entered the typed world of lambda calculus, where do we go from here?

The lambda cube was introduced by the logician Hendrik Barendregt in 1991. The simply typed lambda calculus is found in the lower-left corner.
The other extensions include:
– System-F (polymorphism): terms allowed to depend on types
– Lambda-P (dependent types): types depending on terms
– System-F omega (type operators): types depending on types, corresponding to type operators
The system in the upper-right corner is called Calculus of Constructions and is the most powerful system, both on the logical and computational side.