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 and Tr 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 that x has type T."

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 to t2, if we know that t1 can make progress (be simplified on its own) to t1', 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) to 5, 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 to 1, and the reason we can do that simplification is using rule E-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 answer 6.

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)  :  ⟦𝔹⟧→𝔹