Simply Typed Lambda Calculus Rules
Table of Contents
Simply Typed Lambda Calculus Definition
Consider the following simply typed lambda calculus, with boolean, number, and list extensions. We define evaluation rules and typing rules later in the document.
Equality is only directly provided for abstractions (lambdas) and numbers.
t ::= x | λx:T.t | (t t) | true | false | if t t t | t = t | ~t | <integers> | t + t | t - t | t * t | t < t | t > t | nil T | cons t t | head t | tail t | isnil t | fix t T ::= T → T | 𝔹 | ℤ | ⟦T⟧ v ::= λx:T.t | true | false | <integers> | nil T | cons t t | fix t
Evaluation Rules
Here are the evaluation rules.
Core Calculus
t1 → t1' E-App1 -------------------- (t1 t2) → (t1' t2) t2 → t2' E-App2 -------------------- (v1 t2) → (v1 t2') E-App-Abs ------------------------- ((λx:T.t) v) → t[x ↦ v]
Boolean
t1 → t1' E-If ------------------------------ if t1 t2 t3 → if t1' t2 t3 E-If-true ---------------------- if true t2 t3 → t2 E-If-false ----------------------- if false t2 t3 → t3 t → t' E-Neg1 ------------ ~ t → ~ t' E-Neg-T ---------------- ~ true → false E-Neg-F ---------------- ~ false → true
Equality
t1 → t1' E-Eq1 ---------------------- t1 = t2 → t1' = t2 t2 → t2' E-Eq2 -------------------- v = t2 → v = t2' (note: alpha-equivalent effectively means "the same structure up to which variable names were chosen". If you can rename variables and get to the same term, they are alpha-equivalent.) (λx1:T.t1 and λx2:T.t2 are alpha-equivalent) E-λ-Eq ---------------------------------------------- (λ x1:T. t1) = (λ x2:T. t2) → true (λx1:T.t1 and λx2:T.t2 are NOT alpha-equivalent) E-λ-Neq -------------------------------------------------- (λ x1 . t1) = (λ x2. t2) → false
Numbers
t1 → t1' E-Add1 ---------------------- t1 + t2 → t1' + t2 t2 → t2' E-Add2 ----------------------- t1 + t2 → t1 + t2' E-Add ------------------------------- v1 + v2 → (perform addition) - Mul(*), Sub(=), LT(<),GT(>) follow same pattern as Add(+). - LT/GT compare numbers and result in a boolean.
Lists
t → t' E-Head-1 ------------------ head t → head t' E-Head ------------------------ head (cons t1 t2) → t1 t → t' E-Tail-1 ------------------ tail t → tail t' E-Tail ------------------------ tail (cons t1 t2) → t2 t → t' E-Isnil-1 -------------------- isnil t → isnil t' E-isnil-T ---------------------- isnil (nil T) → true E-isnil-F ---------------------------- isnil (cons t1 t2) → false
Recursion
E-Fix ---------------------------------------- fix (λx:T.t) → t[x ↦ (fix (λx:T.t))]
Typing Rules
Here are the typing rules. They are primarily concerned with maintaining the environment, =Γ=q, and invoking the typing rules on all subterms to synthesize the overall type.
As usual, there is normally only one rule per term for typing rules.
Notes
Some naming notes:
Td
andTr
indicate "type of the function's domain" and "type of the function's range".- the "turnstile" (⊢) should be read as "derives that".
- Example:
Γ⊢x:T
is read as, "Gamma derives thatx
has typeT
."
- Example:
Core Calculus
Ty-var (v,ty) ∈ Γ ------------ Γ ⊢ v : ty Ty-λ (Γ,(v,Td)) ⊢ t:Tr ------------------------------ Γ ⊢ (λ v:Td. t) : Td → Tr Ty-app Γ⊢t1 : Td→Tr Γ ⊢ t2 : Td ----------------------------- Γ ⊢ (t1 t2) : Tr
Boolean
Ty-true ------------- ⊢ true : 𝔹 Ty-false ------------- ⊢ false : 𝔹 Γ⊢ t1:𝔹 , Γ⊢ t2:T, Γ⊢ t3:T Ty-if ------------------------------- Γ ⊢ if t1 t2 t3 : T Γ⊢ t:𝔹 Ty-neg ------------------ Γ ⊢ ~ t : 𝔹
Equality
Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-Int-Eq ---------------------- Γ⊢ t1 = t2 : 𝔹 Γ⊢ t1:T1→T2, Γ⊢ t2:T1→T2 Ty-λ-Eq -------------------------- Γ⊢ t1 = t2 : 𝔹
Numbers
Ty-Int ------------------- ⊢ <integer> : ℤ Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-Add -------------------- Γ⊢ t1+t2 : ℤ Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-Sub -------------------- Γ⊢ t1-t2 : ℤ Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-Mul -------------------- Γ⊢ t1*t2 : ℤ Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-LT -------------------- Γ⊢ t1<t2 : 𝔹 Γ⊢ t1:ℤ , Γ⊢ t2:ℤ Ty-GT -------------------- Γ⊢ t1>t2 : 𝔹
Lists
Ty-nil ----------------- ⊢ nil ⟦T⟧ : ⟦T⟧ Γ⊢ t1:T , Γ⊢ t2:⟦T⟧ Ty-cons ------------------------ Γ⊢ (cons t1 t2) : ⟦T⟧ Γ⊢ t:⟦T⟧ Ty-head ----------------- Γ⊢ head t : T Γ⊢ t:⟦T⟧ Ty-tail ------------------ Γ⊢ tail t : ⟦T⟧ Γ⊢ t:⟦T⟧ Ty-isnil ------------------ Γ⊢ isnil t : 𝔹
Recursion
Γ⊢ t:T→T Ty-Fix --------------- Γ⊢ fix t : T
Examples
Example Evaluation
Let's reduce one expression down to a value together, as an example of the formatting we want to see. Note that evaluation ignores all type ascriptions, but does preserve them in-place.
(((λa:ℤ. λb:ℤ. a + b) 5) 8) ==> E-App1 (via E-App-Abs) ((λb:ℤ. 5 + b) 8) ==> E-App-Abs 5 + 8 ==> E-Add 13
Notice in the first step, the overall term is experiencing an E-App1
transformation because we're making progress on the first term. But
the way that it makes progress on the first term is by applying
E-App-Abs
to it. So we have written it as E-App1 (using E-App-Abs)
.
Also, note that we didn't put in some optional parentheses. The two
examples below are equivalent, and I will usually prefer the
second. The concept here is that a lambda value grabs as much as it
can for its body term, often only ceasing when it finds a
closing-parenthesis that it didn't open itself. So λb
grabs a+b
as
its body, and λa
grabs (λb:ℤ. (a+b))
as its body.
( (λa:ℤ. (λb:ℤ. (a+b))) expr) ( (λa:ℤ. λb:ℤ. a+b ) expr)
Understanding Evaluation Rules
In order to make sense of these rules, we need to understand their meaning. We will give some examples of the most common rules.
calling a function: E-App-Abs
Consider this example:
((λb:ℤ. 5 + b) 8)
What we see is a lambda expression applied to the value
8
. Rephrased, 8
is the argument to the (λb:ℤ.5+b)
function; we
will use the argument for the parameter b
and reduce the whole thing
down to that lambda's body (5+b)
, except all occurrences of b
in
that body will be replaced with 8
.
Here is the E-App-Abs
rule (evaluation of an application of an
abstraction), which says the same thing, formally, about any
application of a lambda to a value:
E-App-Abs ---------------------------------- ((λ x:T. t) v) → t[x↦v]
We could verbalize this rule by saying: "when a lambda abstraction
(λx:T.t)
is applied to a value v
, it reduces to the body of the
lambda, t
, with all occurrences of its parameter x
replaced with
the argument-value v
."
For the specific example we viewed, the rule's x
was b
; the rule's
t
was (5+b)
; the rule's v
was 8
. We can then truly do a
search-and-replace of our verbalization: "when a lambda abstraction
(λb:ℤ.5+b)
is applied to a value 8
, it reduces to the body of the
lambda, 5+b
, with all occurrences of its parameter b
replaced with
the argument-value 8
."
making progress on subterms: E-App1
Suppose we have an application term, but the left sub-term is not
actually a lambda; instead, there's some computation there that could
be reduced to eventually become a lambda. We can't yet apply
E-App-Abs
, because it doesn't match the lambda-applied-to-value
shape. But as long as that left subterm can make some progress, we're
allowed to simplify that part of the term and leave the rest of the
overall application alone.
Consider this actual term:
( ((λx:ℤ.λy:ℤ.x+y) 1) 5 ) ------------------- -
It is an application; we've underlined its two subterms. The left
subterm is ((λx:ℤ.λy:ℤ.x+y) 1)
, which is itself an application term;
the right subterm is the value 5
. Since we don't have an actual
lambda value on the left yet, we can't apply E-App-Abs
. But we know
that we can make some progress on the left subterm. Specifically,
((λx:ℤ.λy:ℤ.x+y) 1)
can be reduced to (λy:ℤ.1+y)
.
Now let's visit the actual rule, E-App1
:
t1 → t1' ---------------------- (t1 t2) → (t1' t2)
In general, it says:
- "when we consider an application of
t1
tot2
, if we know thatt1
can make progress (be simplified on its own) tot1
', then we can simplify the overall application term(t1 t2)
to(t1' t2)
."
For our specific example, the rule's t1
is ((λx:ℤ.λy:ℤ.x+y) 1)
,
and the rule's t2
is 5
. We can then re-state the rule to directly
describe why our example term can use the rule:
- "when we consider an application of
((λx:ℤ.λy:ℤ.x+y) 1)
to5
, if we know that((λx:ℤ.λy:ℤ.x+y) 1)
can make progress (be simplified) to(λy:ℤ.1+y)
, then we can simplify the overall application term(((λx:ℤ.λy:ℤ.x+y) 1) 5)
to((λy:ℤ.1+y) 5)
."
Putting it together:
When simplifying the expression and showing what rules were applied, we now can make more sense of it:
(((λx:ℤ.λy:ℤ.x+y) 1) 5) ==> E-App1 (via E-App-Abs) ((λy:ℤ.1+y) 5) ==> E-App-Abs (1+5) ==> E-Add 6
This claims:
- our overall term will first use
E-App1
to simplify the double-lambda applied to1
, and the reason we can do that simplification is using ruleE-App-Abs
. - then, our overall term is an application of a lambda to a value, so
we can directly apply
E-App-Abs
. - lastly, we use our addition rule (
E-Add
) to perform the addition, getting our answer6
.
Example Typechecking
Suppose we want to know whether the following term is well-typed (typing rules are able to assign it a type, indicating it obeys our language's typing rules).
( (λx:ℤ.x+1) 3)
The term is an application, so regardless of the subterms themselves,
we will try to use the Ty-App
rule. Generally we then need to
recursively consider applying other typing rules to the subterms.
The reasonable way to apply our rules is to draw a proof tree, where the typing rules are instantiated with the actual terms/subterms of our program, with the overall desired term at the bottom, and all tops (leaves) of the tree having no antecedents (nothing above the line). Here is the proof tree for our example:
------------Ty-Var -------------Ty-Int {(x,ℤ)}⊢ x:ℤ {(x,ℤ)}⊢1:ℤ -----------------------------------Ty-Add {(x,ℤ)}⊢(x+1):ℤ -------------------Ty-λ --------Ty-Int {} ⊢ (λx:ℤ.x+1):ℤ→ℤ {}⊢3:ℤ -------------------------------------------------Ty-App {} ⊢ ((λx:ℤ.x+1) 3) : ℤ
The bottom two rows of text can be read together as a use of Ty-App,
perhaps being read aloud as: "If an empty environment can show that
(λx:ℤ.x+1)
has type ℤ→ℤ
, and if an empty environment can
show that 3
has type ℤ
, then an empty environment can show
that ((λx:ℤ.x+1) 3)
has type ℤ
."
Example Type Rule Usage
Suppose we want to typecheck the following term:
λ x:⟦𝔹⟧ . head x
It is a lambda-expression that accepts a list of booleans, and returns the head item of that list.
Let's look at the Ty-λ
rule:
ΓU(x,Td) ⊢ t:Tr Ty-Abs ------------------------- Γ⊢ (λx:Td.t) : Td->Tr
We currently have no environment (our expression isn't inside of any
other lambdas), so Γ is empty. Matching up parts of our actual
expression with the general format in the Ty-Abs
rule, we see that
our variable is conveniently also named x
, but its type is ⟦𝔹⟧
instead of Td
. The rule's lambda-body is called t
, and our term's
body is head x
.
We need to show the claim above the line in order to claim the type
conclusion below the line (that our term is of type Td->Tr
). This
means that we need to show that extending our environment, Γ, with
(x,Td)
is sufficient to find out that the body is of some type that
the rule just calls Tr
. What will it be, specifically, in our case?
We are now looking at head x
, wondering what type it is. We look in
similar fashion back to the Ty-Head
rule:
Γ⊢ t : ⟦T⟧ Ty-Head ----------------- Γ⊢ head t : T
Since our t
term is actually the variable x
, we just look it up in
our environment and see that its type is ⟦𝔹⟧
. Great - so the
resulting type of head x
is 𝔹
. Okay, back to the overall Ty-Abs
rule.
Since our lambda's body was found to be of type 𝔹
, that stands in
for Tr
; so our overall type is ⟦𝔹⟧→𝔹
.
We can try to draw out all the rules in a sort of upwards-cascading tree shape:
(x,⟦𝔹⟧) ∈ {(x,⟦𝔹⟧)} --------------------- Ty-Var {(x,⟦𝔹⟧)} ⊢ x : ⟦𝔹⟧ ---------------------------- Ty-Head {(x,⟦𝔹⟧)} ⊢ head x : 𝔹 --------------------------------- Ty-Abs {}⊢ (λx:⟦𝔹⟧. head x) : ⟦𝔹⟧→𝔹