Week 7: Simply-Typed Lambda Calculus (STLC)
- Author
- License
No redistribution allowed (usage by permission in CS-428).
Introduction
The last few chapters have focused on small programming languages that are representative of the essence of the imperative languages. We now turn to lambda-calculus, the usual representative of functional languages.
Notation var := string. Definition var_eq : forall x y : var, {x = y} + {x <> y} := string_dec. Infix "==v" := var_eq (no associativity, at level 50).
The simply typed lambda-calculus (STLC) is a tiny core calculus embodying the key concept of functional abstraction. This concept shows up in pretty much every real-world programming language in some form (functions, procedures, methods, etc.).
We will follow a standard pattern: we formalize the calculus (syntax, small-step semantics), we define the typing rules, and finally we prove interesting properties about the type system (progress and preservation).
The new technical challenges arise from the mechanisms of variable binding and substitution. It will take some work to deal with these.
Module Stlc.
Syntax
In our version of STLC, we have natural number constants and addition. This will let us define a simple type systems that checks whether something evaluates to a natural number. If you are used to a simpler definition of Lambda calculus, we will see as exercises how to encode natural numbers using the more basic lambda calculus.
Inductive exp : Set := | Var (x : var) | Const (n : nat) | Plus (e1 e2 : exp) | Abs (x : var) (e1 : exp) | App (e1 e2 : exp).
The STLC is a language of higher-order functions: we can write down functions that take other functions as arguments and/or return other functions as results.
The STLC doesn't provide any primitive syntax for defining named functions: i.e., all functions are "anonymous." But they could easily be introduced: indeed, the fundamental naming and binding mechanisms are exactly the same.
The STLC lives in the lower-left front corner of the famous lambda cube (also called the Barendregt Cube), which visualizes three sets of features that can be added to its simple core:
(*
Calculus of Constructions
type operators +--------+
/| /|
/ | / |
polymorphism +--------+ |
| | | |
| +-----|--+
| / | /
|/ |/
+--------+ dependent types
STLC
*)
Moving from bottom to top in the cube corresponds to adding
polymorphic types like forall X, X -> X
. Adding just
polymorphism gives us the famous Girard-Reynolds calculus, System F.
(terms bind types)
Moving from front to back corresponds to adding type operators
like list
. (types bind types)
Moving from left to right corresponds to adding dependent types
like forall n, array-of-size n
. (types bind terms)
The top right corner on the back, which combines all three features, is called the Calculus of Constructions. First studied by Coquand and Huet, it forms the foundation of Coq's logic. Coq's logic is the Calculus of Inductive Constructions (CIC), a variant of the Calculus of COnstructions.
Semantics
To define the small-step semantics of STLC terms, we begin, as always, by defining the set of values. Next, we define the critical notion of substitution, which is used in the reduction rule for application expressions. And finally we give the small-step relation itself.
Inductive value : exp -> Prop := | VConst : forall n, value (Const n) | VAbs : forall x e1, value (Abs x e1).
Recall that value correspond to terms that have no more work to do. Clearly, constants are value, and applications are not.
For abstractions, we have a choice:
We can say that
\x.t
is a value only whent
is a value -- i.e., only if the function's body has been reduced (as much as it can be without knowing what argument it is going to be applied to).Or we can say that
\x.t
is always a value, no matter whethert
is one or not -- in other words, we can say that reduction stops at abstractions.
Our usual way of evaluating expressions in Gallina makes the first choice -- for example,
But Gallina is rather unusual in this respect. Most real-world functional programming languages make the second choice -- reduction of a function's body only begins when the function is actually applied to an argument.
We also make the second choice here.
Now we come to the heart of the STLC: the operation of substituting one term for a variable in another term. This operation is used below to define the operational semantics of function application, where we will need to substitute the argument term for the function parameter in the function's body.
Here subst e1 x e2
replaces all occurences of x
in e2
with e1
.
Here we define a functional version, but we could very well have defined an inductive proposition.
subst
is not applicable when e1
is not closed, to avoid some complexity we don't need.
Fixpoint subst (e1 : exp) (x : string) (e2 : exp) : exp := match e2 with | Var y => if y ==v x then e1 else Var y | Const n => Const n | Plus e2' e2'' => Plus (subst e1 x e2') (subst e1 x e2'') | Abs y e2' => Abs y (if y ==v x then e2' else subst e1 x e2') | App e2' e2'' => App (subst e1 x e2') (subst e1 x e2'') end. Inductive step : exp -> exp -> Prop := (* These rules show the real action of the semantics. *) | Beta : forall x e v, value v -> step (App (Abs x e) v) (subst v x e) | Add : forall n1 n2, step (Plus (Const n1) (Const n2)) (Const (n1 + n2)) (* Then we have a bunch of bureaucratic, repetitive rules encoding evaluation order. See next lecture for how to streamline this part, but for now note that the [value] premises below are crucial to enforce a single order of evaluation. *) | App1 : forall e1 e1' e2, step e1 e1' -> step (App e1 e2) (App e1' e2) | App2 : forall v e2 e2', value v -> step e2 e2' -> step (App v e2) (App v e2') | Plus1 : forall e1 e1' e2, step e1 e1' -> step (Plus e1 e2) (Plus e1' e2) | Plus2 : forall v e2 e2', value v -> step e2 e2' -> step (Plus v e2) (Plus v e2').
Some helpful notations to define programs.
Coercion Const : nat >-> exp. Infix "^+^" := Plus (at level 50). Coercion Var : var >-> exp. Notation "\ x , e" := (Abs x e) (at level 51). Infix "@" := App (at level 49, left associativity).
Believe it or not, this is a Turing-complete language! Here's an example nonterminating program.
Example omega := (\"x", "x" @ "x") @ (\"x", "x" @ "x").step omega omegastep omega omegaconstructor. Qed.value (\ "x", "x" @ "x")
Types and Typing Environments
That language is suitable to describe with a static type system. Here's our modest, but countably infinite, set of types.
Inductive type := | Nat (* Numbers *) | Fun (dom ran : type) (* Functions *).
Although we are primarily interested in the binary relation
relating a closed term t
to its type T
, we need
to generalize a bit to make the definitions work.
Consider checking that \a,tb
has type
TA->TB
. Intuitively, we need to check that tb
has type
TB
. However, we have removed the binder \a
, so a
may occur
free in tb
(that is, tb
may be open). While checking that
tb
has type TB
, we must remember that a
has type TA
, in
order to deal with these free occurrences of a
. Similarly, tb
itself could contain abstractions, and typechecking their bodies
could require looking up the declared types of yet more free
variables.
To keep track of all this, we add a third element to the relation, a typing context, which records the types of the variables that may occur free in a term -- that is, a partial map from variables to types.
Let us write these type maps and their operations.
Definition type_map: Type := var -> option type. Definition empty_map : type_map := (fun v => None). Notation "$0" := empty_map. Definition tmap_get (tm:type_map) (v:var): option type := tm v. Infix "$?" := tmap_get (no associativity, at level 50). Definition tmap_set (tm:type_map) (vt:var * type) := fun v => if (v ==v fst vt) then Some (snd vt) else tm v. Infix "$+" := tmap_set (no associativity, at level 50).forall (tm : type_map) (v : var) (t : type), (tm $+ (v, t)) $? v = Some tforall (tm : type_map) (v : var) (t : type), (tm $+ (v, t)) $? v = Some ttm: type_map
v: var
t: type(tm $+ (v, t)) $? v = Some ttm: type_map
v: var
t: type(if v ==v fst (v, t) then Some (snd (v, t)) else tm v) = Some ttm: type_map
v: var
t: type(if v ==v v then Some t else tm v) = Some ttm: type_map
v: var
t: type(if string_dec v v then Some t else tm v) = Some ttm: type_map
v: var
t: type
n: v <> vtm v = Some ttm: type_map
v: var
t: type
n: v <> vFalseauto. Qed.tm: type_map
v: var
t: type
n: v <> vv = vforall (tm : type_map) (v1 v2 : var) (t : type), v1 <> v2 -> (tm $+ (v1, t)) $? v2 = tm $? v2forall (tm : type_map) (v1 v2 : var) (t : type), v1 <> v2 -> (tm $+ (v1, t)) $? v2 = tm $? v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2(tm $+ (v1, t)) $? v2 = tm $? v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2(if v2 ==v fst (v1, t) then Some (snd (v1, t)) else tm v2) = tm v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2(if v2 ==v v1 then Some t else tm v2) = tm v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2(if string_dec v2 v1 then Some t else tm v2) = tm v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2
e: v2 = v1Some t = tm v2tm: type_map
v1, v2: var
t: type
H: v1 <> v2
e: v2 = v1Falseauto. Qed.tm: type_map
v1, v2: var
t: type
H: v1 <> v2
e: v2 = v1v1 = v2forall (tm : type_map) (v : var) (t1 t2 : type) (v' : var), ((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'forall (tm : type_map) (v : var) (t1 t2 : type) (v' : var), ((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'tm: type_map
v: var
t1, t2: type
v': var((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'tm: type_map
v: var
t1, t2: type
v': var
e: v' = v((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'tm: type_map
v: var
t1, t2: type
v': var
n: v' <> v((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'tm: type_map
v: var
t1, t2: type
v': var
e: v' = v((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'tm: type_map
v: var
t1, t2: type((tm $+ (v, t1)) $+ (v, t2)) $? v = (tm $+ (v, t2)) $? vauto.tm: type_map
v: var
t1, t2: typeSome t2 = Some t2repeat (rewrite tmap_gso; auto). Qed.tm: type_map
v: var
t1, t2: type
v': var
n: v' <> v((tm $+ (v, t1)) $+ (v, t2)) $? v' = (tm $+ (v, t2)) $? v'forall (tm : type_map) (v1 : var) (t1 : type) (v2 : var) (t2 : type) (v' : var), v1 <> v2 -> ((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'forall (tm : type_map) (v1 : var) (t1 : type) (v2 : var) (t2 : type) (v' : var), v1 <> v2 -> ((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'tm: type_map
v1: var
t1: type
v2: var
t2: type
v': var
H: v1 <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'tm: type_map
t1: type
v2: var
t2: type
H: v2 <> v2((tm $+ (v2, t1)) $+ (v2, t2)) $? v2 = ((tm $+ (v2, t2)) $+ (v2, t1)) $? v2tm: type_map
v1: var
t1: type
v2: var
t2: type
H, n: v1 <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v1 = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v1tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
n: v2 <> v1((tm $+ (v1, t1)) $+ (v2, t2)) $? v2 = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v2tm: type_map
v1: var
t1: type
v2: var
t2: type
v': var
H: v1 <> v2
n: v' <> v1
n0: v' <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'tm: type_map
t1: type
v2: var
t2: type
H: v2 <> v2((tm $+ (v2, t1)) $+ (v2, t2)) $? v2 = ((tm $+ (v2, t2)) $+ (v2, t1)) $? v2tm: type_map
t1: type
v2: var
t2: type
H: v2 <> v2Falseauto.tm: type_map
t1: type
v2: var
t2: type
H: v2 <> v2v2 = v2tm: type_map
v1: var
t1: type
v2: var
t2: type
H, n: v1 <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v1 = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v1tm: type_map
v1: var
t1: type
v2: var
t2: type
H, n: v1 <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v1 = Some t1apply tmap_gss.tm: type_map
v1: var
t1: type
v2: var
t2: type
H, n: v1 <> v2(tm $+ (v1, t1)) $? v1 = Some t1tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
n: v2 <> v1((tm $+ (v1, t1)) $+ (v2, t2)) $? v2 = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v2tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
n: v2 <> v1Some t2 = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v2tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
n: v2 <> v1Some t2 = (tm $+ (v2, t2)) $? v2apply tmap_gss.tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
n: v2 <> v1(tm $+ (v2, t2)) $? v2 = Some t2repeat (rewrite tmap_gso; auto). Qed.tm: type_map
v1: var
t1: type
v2: var
t2: type
v': var
H: v1 <> v2
n: v' <> v1
n0: v' <> v2((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'
In one proof later, it will be convenient to talk about typing contexts being equal if they just map each variable to the same type option.
But take the lemma above: although the two maps return the value for any var v'
, the two functions are not equal in the sense of Coq equality.
Ley's make a quick detour through functional extensionality.
Module Ext.
Functional Extensionality
Coq's logic is quite minimalistic. This means that one occasionally encounters cases where translating standard mathematical reasoning into Coq is cumbersome -- or even impossible -- unless we enrich its core logic with additional axioms.
For example, the equality assertions that we have seen so far mostly have concerned elements of inductive types (nat, bool, etc.). But, since Coq's equality operator is polymorphic, we can use it at _any_ type -- in particular, we can write propositions claiming that two _functions_ are equal to each other:
(fun x : nat => 3 + x) = (fun x : nat => Nat.pred 4 + x)reflexivity. Qed.(fun x : nat => 3 + x) = (fun x : nat => Nat.pred 4 + x)
These two functions are equal just by simplification, but in general functions can be equal for more interesting reasons.
In common mathematical practice, two functions are considered equal if they produce the same output on every input: This is known as the principle of functional extensionality.
Informally, an "extensional property" is one that pertains to an object's observable behavior. Thus, functional extensionality simply means that a function's identity is completely determined by what we can observe from it -- i.e., the results we obtain after applying it.
However, functional extensionality is not part of Coq's built-in logic. This means that some intuitively obvious propositions are not provable.
(fun x : nat => x + 1) = (fun x : nat => 1 + x)(fun x : nat => x + 1) = (fun x : nat => 1 + x)(fun x : nat => x + 1) = (fun x : nat => S x)Abort.(fun x : nat => x + 1) = (fun x : nat => S x)
However, if we like, we can add functional extensionality to Coq's
core using the Axiom
command.
Axiom functional_extensionality : forall {X Y: Type}
{f g : X -> Y},
(forall (x:X), f x = g x) -> f = g.
Defining something as an Axiom
has the same effect as stating a
theorem and skipping its proof using Admitted
, but it alerts the
reader that this isn't just something we're going to come back and
fill in later!
We can now invoke functional extensionality in proofs:
(fun x : nat => x + 1) = (fun x : nat => 1 + x)(fun x : nat => x + 1) = (fun x : nat => 1 + x)forall x : nat, x + 1 = 1 + xapply PeanoNat.Nat.add_comm. Qed.x: natx + 1 = 1 + x
Coq provides a nice way to see on which axioms, parameters and admitted proofs each of your theorems depend on.
For instance, it can show that the theorem function_equality_ex2
only depends on the functional extensionality axiom.
Naturally, we need to be quite careful when adding new axioms into Coq's logic, as this can render it inconsistent -- that is, it may become possible to prove every proposition, including False, 2+2=5, etc.!
In general, there is no simple way of telling whether an axiom is safe to add: hard work by highly trained mathematicians is often required to establish the consistency of any particular combination of axioms.
Fortunately, it is known that adding functional extensionality, in particular, is consistent.
For this reason, it's available in the standard library (Logic.FunctionalExtensionality).
Other examples of known consistent axioms include the functional axiom of choice and proof irrelevance. See more details here https://github.com/coq/coq/wiki/The-Logic-of-Coq#axioms.
End Ext.forall (tm : type_map) (v1 : var) (t1 : type) (v2 : var) (t2 : type), v1 <> v2 -> (tm $+ (v1, t1)) $+ (v2, t2) = (tm $+ (v2, t2)) $+ (v1, t1)forall (tm : type_map) (v1 : var) (t1 : type) (v2 : var) (t2 : type), v1 <> v2 -> (tm $+ (v1, t1)) $+ (v2, t2) = (tm $+ (v2, t2)) $+ (v1, t1)tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2(tm $+ (v1, t1)) $+ (v2, t2) = (tm $+ (v2, t2)) $+ (v1, t1)tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2forall x : var, ((tm $+ (v1, t1)) $+ (v2, t2)) x = ((tm $+ (v2, t2)) $+ (v1, t1)) xtm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
x: var((tm $+ (v1, t1)) $+ (v2, t2)) x = ((tm $+ (v2, t2)) $+ (v1, t1)) xtm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
x: var
H0: forall v' : var, v1 <> v2 -> ((tm $+ (v1, t1)) $+ (v2, t2)) $? v' = ((tm $+ (v2, t2)) $+ (v1, t1)) $? v'((tm $+ (v1, t1)) $+ (v2, t2)) x = ((tm $+ (v2, t2)) $+ (v1, t1)) xauto. Qed.tm: type_map
v1: var
t1: type
v2: var
t2: type
H: v1 <> v2
x: var
H0: forall v' : var, v1 <> v2 -> ((tm $+ (v1, t1)) $+ (v2, t2)) v' = ((tm $+ (v2, t2)) $+ (v1, t1)) v'((tm $+ (v1, t1)) $+ (v2, t2)) x = ((tm $+ (v2, t2)) $+ (v1, t1)) x
Typing System
Inductive has_ty : type_map -> exp -> type -> Prop := | HtVar : forall G x t, G $? x = Some t -> has_ty G (Var x) t | HtConst : forall G n, has_ty G (Const n) Nat | HtPlus : forall G e1 e2, has_ty G e1 Nat -> has_ty G e2 Nat -> has_ty G (Plus e1 e2) Nat | HtAbs : forall G x e1 t1 t2, has_ty (G $+ (x, t1)) e1 t2 -> has_ty G (Abs x e1) (Fun t1 t2) | HtApp : forall G e1 e2 t1 t2, has_ty G e1 (Fun t1 t2) -> has_ty G e2 t1 -> has_ty G (App e1 e2) t2. Local Hint Constructors value step has_ty : core. (* some notation for the typing judgement *) Notation "Gamma '|--' e '\in' T" := (has_ty Gamma e T) (at level 101). Infix "-->" := Fun (at level 60, right associativity). (* Some examples of typed programs *)$0 |-- 1 ^+^ 1 \in Nateauto. Qed.$0 |-- 1 ^+^ 1 \in Nat$0 |-- \ "n", (\ "m", "n" ^+^ "m") \in Nat --> Nat --> Natrepeat (econstructor; simpl). Qed.$0 |-- \ "n", (\ "m", "n" ^+^ "m") \in Nat --> Nat --> Nat$0 |-- (\ "n", (\ "m", "n" ^+^ "m")) @ 7 @ 4 \in Natrepeat (econstructor; simpl). Qed.$0 |-- (\ "n", (\ "m", "n" ^+^ "m")) @ 7 @ 4 \in Nat$0 |-- (\ "x", "x") @ (\ "x", "x") @ 7 \in Natrepeat (econstructor; simpl). Qed.$0 |-- (\ "x", "x") @ (\ "x", "x") @ 7 \in Nat
Type Soundness
What useful invariants could we prove about programs in this language? How about that, at every point, either they're finished executing or they can take further steps? For instance, a program that tried to add a function to a number would not satisfy that condition, and we call it stuck. We want to prove that typed programs can never become stuck. Here's a good invariant to strive for.
Definition unstuck e := value e \/ (exists e' : exp, step e e').
We can now define some useful lemmas about typed values.
forall e : exp, ($0 |-- e \in Nat) -> value e -> exists n : nat, e = nforall e : exp, ($0 |-- e \in Nat) -> value e -> exists n : nat, e = ne: exp
H: $0 |-- e \in Nat
H0: value eexists n : nat, e = neauto. Qed.n: nat
H0: value n
H: $0 |-- n \in Natexists n0 : nat, n = n0forall (e : exp) (t1 t2 : type), ($0 |-- e \in t1 --> t2) -> value e -> exists (x : var) (u : exp), e = \ x, uforall (e : exp) (t1 t2 : type), ($0 |-- e \in t1 --> t2) -> value e -> exists (x : var) (u : exp), e = \ x, ue: exp
t1, t2: type
H: $0 |-- e \in t1 --> t2
H0: value eexists (x : var) (u : exp), e = \ x, ueauto. Qed.t1, t2: type
x: var
e1: exp
H0: value (\ x, e1)
H: $0 |-- \ x, e1 \in t1 --> t2
H3: $0 $+ (x, t1) |-- e1 \in t2exists (x0 : var) (u : exp), \ x, e1 = \ x0, u
The progress theorem tells us that closed, well-typed terms are not stuck: either a well-typed term is a value, or it can take a reduction step.
forall (e : exp) (t : type), ($0 |-- e \in t) -> unstuck eforall (e : exp) (t : type), ($0 |-- e \in t) -> unstuck ee: exp
t: type
H: $0 |-- e \in tunstuck ee: exp
t: type
Gamma: type_map
HeqGamma: Gamma = $0
H: Gamma |-- e \in tunstuck ee: exp
t: type
Gamma: type_map
HeqGamma: Gamma = $0
H: Gamma |-- e \in tvalue e \/ (exists e' : exp, step e e')e: exp
t: type
Gamma: type_map
HeqGamma: Gamma = $0
H: Gamma |-- e \in t
H0: $0 = $0value e \/ (exists e' : exp, step e e')x: var
t: type
H: $0 $? x = Some t
H0: $0 = $0value x \/ (exists e' : exp, step x e')e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')value (e1 ^+^ e2) \/ (exists e' : exp, step (e1 ^+^ e2) e')e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')value (e1 @ e2) \/ (exists e' : exp, step (e1 @ e2) e')x: var
t: type
H: $0 $? x = Some t
H0: $0 = $0value x \/ (exists e' : exp, step x e')inversion H.x: var
t: type
H: None = Some t
H0: $0 = $0value x \/ (exists e' : exp, step x e')e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')value (e1 ^+^ e2) \/ (exists e' : exp, step (e1 ^+^ e2) e')e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: exists e' : exp, step e1 e'exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: exists e' : exp, step e1 e'exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
x: exp
H2: step e1 xexists e' : exp, step (e1 ^+^ e2) e'apply Plus1; eauto.e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
x: exp
H2: step e1 xstep (e1 ^+^ e2) ?e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: value e2
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: value e2
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: value e2
H2: value e1
n1: nat
He1: e1 = n1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: value e2
H2: value e1
n1: nat
He1: e1 = n1
n2: nat
He2: e2 = n2exists e' : exp, step (e1 ^+^ e2) e'n2: nat
H1: $0 |-- n2 \in Nat
n1: nat
H: $0 |-- n1 \in Nat
H0: $0 = $0
H3: value n2
H2: value n1exists e' : exp, step (n1 ^+^ n2) e'apply Add.n2: nat
H1: $0 |-- n2 \in Nat
n1: nat
H: $0 |-- n1 \in Nat
H0: $0 = $0
H3: value n2
H2: value n1step (n1 ^+^ n2) ?e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value e1exists e' : exp, step (e1 ^+^ e2) e'e1, e2: exp
H1: $0 |-- e2 \in Nat
H: $0 |-- e1 \in Nat
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value e1
n1: nat
He1: e1 = n1exists e' : exp, step (e1 ^+^ e2) e'e2: exp
H1: $0 |-- e2 \in Nat
n1: nat
H: $0 |-- n1 \in Nat
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value n1exists e' : exp, step (n1 ^+^ e2) e'e2: exp
H1: $0 |-- e2 \in Nat
n1: nat
H: $0 |-- n1 \in Nat
H0: $0 = $0
x: exp
H3: step e2 x
H2: value n1exists e' : exp, step (n1 ^+^ e2) e'apply Plus2; eauto.e2: exp
H1: $0 |-- e2 \in Nat
n1: nat
H: $0 |-- n1 \in Nat
H0: $0 = $0
x: exp
H3: step e2 x
H2: value n1step (n1 ^+^ e2) ?e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')value (e1 @ e2) \/ (exists e' : exp, step (e1 @ e2) e')e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: $0 = $0 -> value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: $0 = $0 -> value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
IHhas_ty1: value e1 \/ (exists e' : exp, step e1 e')exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: exists e' : exp, step e1 e'exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: exists e' : exp, step e1 e'exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
x: exp
H2: step e1 xexists e' : exp, step (e1 @ e2) e'apply App1; eauto.e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
x: exp
H2: step e1 xstep (e1 @ e2) ?e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
IHhas_ty2: value e2 \/ (exists e' : exp, step e2 e')
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
H3: value e2
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
H3: value e2
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
H3: value e2
H2: value e1
x1: var
u1: exp
Hf1: e1 = \ x1, u1exists e' : exp, step (e1 @ e2) e'e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
x1: var
u1: exp
H: $0 |-- \ x1, u1 \in t1 --> t2
H0: $0 = $0
H3: value e2
H2: value (\ x1, u1)exists e' : exp, step ((\ x1, u1) @ e2) e'apply Beta; auto.e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
x1: var
u1: exp
H: $0 |-- \ x1, u1 \in t1 --> t2
H0: $0 = $0
H3: value e2
H2: value (\ x1, u1)step ((\ x1, u1) @ e2) ?e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
H3: exists e' : exp, step e2 e'
H2: value e1exists e' : exp, step (e1 @ e2) e'e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
x: exp
H3: step e2 x
H2: value e1exists e' : exp, step (e1 @ e2) e'apply App2; eauto. Qed.e1, e2: exp
t1, t2: type
H1: $0 |-- e2 \in t1
H: $0 |-- e1 \in t1 --> t2
H0: $0 = $0
x: exp
H3: step e2 x
H2: value e1step (e1 @ e2) ?e'
This could also be proved by induction on e
(left as exercise).
The other half of the type soundness property is the preservation of types during reduction. For this part, we'll need to develop some technical machinery for reasoning about variables and substitution. Working from top to bottom (from the high-level property we are actually interested in to the lowest-level technical lemmas that are needed by various cases of the more interesting proofs), the story goes like this:
The preservation theorem is proved by induction on a typing derivation. There is one interesting case: the Beta-reduction. Its definition uses the substitution operation. To see that this step preserves typing, we need to know that the substitution itself does. So we prove a...
substitution lemma, stating that substituting a (well-typed) term for a variable in a term preserves the type of the term. The proof requires looking at all the different cases in the definition of substitition. This time, for the variables case, we discover that we need to deduce from the fact that a term
x
has typeT
in the empty context the fact thatx
has typeT
in every context. For this we prove a...weakening lemma, showing that typing is preserved under "extensions" to the typing context.
To make Coq happy, of course, we need to formalize the story in the opposite order, starting with weakening...
Inclusion between typing contexts is preserved by adding the same new mapping to both.
forall (G G' : type_map) (x : var) (t : type), (forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t') -> forall (x' : var) (t' : type), (G $+ (x, t)) $? x' = Some t' -> (G' $+ (x, t)) $? x' = Some t'forall (G G' : type_map) (x : var) (t : type), (forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t') -> forall (x' : var) (t' : type), (G $+ (x, t)) $? x' = Some t' -> (G' $+ (x, t)) $? x' = Some t'G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'(G' $+ (x, t)) $? x' = Some t'G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'
e: x = x'(G' $+ (x, t)) $? x' = Some t'G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'
n: x <> x'(G' $+ (x, t)) $? x' = Some t'G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'
e: x = x'(G' $+ (x, t)) $? x' = Some t'G, G': type_map
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x', t)) $? x' = Some t'(G' $+ (x', t)) $? x' = Some t'G, G': type_map
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x', t)) $? x' = Some t'Some t = Some t'auto.G, G': type_map
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: Some t = Some t'Some t = Some t'G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'
n: x <> x'(G' $+ (x, t)) $? x' = Some t'rewrite tmap_gso in H0; auto. Qed.G, G': type_map
x: var
t: type
H: forall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'
x': var
t': type
H0: (G $+ (x, t)) $? x' = Some t'
n: x <> x'G' $? x' = Some t'
This lemma lets us propagate a typing derivation into a new context that includes the old one.
forall (G : type_map) (e : exp) (t : type), (G |-- e \in t) -> forall G' : type_map, (forall (x : var) (t0 : type), G $? x = Some t0 -> G' $? x = Some t0) -> G' |-- e \in tforall (G : type_map) (e : exp) (t : type), (G |-- e \in t) -> forall G' : type_map, (forall (x : var) (t0 : type), G $? x = Some t0 -> G' $? x = Some t0) -> G' |-- e \in tG: type_map
e: exp
t: type
H: G |-- e \in tforall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e \in tG: type_map
x: var
t: type
H: G $? x = Some t
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- x \in tG: type_map
n: nat
G': type_map
H: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- n \in NatG: type_map
e1, e2: exp
H: G |-- e1 \in Nat
H0: G |-- e2 \in Nat
IHhas_ty1: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e1 \in Nat
IHhas_ty2: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e2 \in Nat
G': type_map
H1: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- e1 ^+^ e2 \in NatG: type_map
x: var
e1: exp
t1, t2: type
H: G $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G' : type_map, (forall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> G' $? x0 = Some t) -> G' |-- e1 \in t2
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- \ x, e1 \in t1 --> t2G: type_map
e1, e2: exp
t1, t2: type
H: G |-- e1 \in t1 --> t2
H0: G |-- e2 \in t1
IHhas_ty1: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e1 \in t1 --> t2
IHhas_ty2: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e2 \in t1
G': type_map
H1: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- e1 @ e2 \in t2G: type_map
x: var
t: type
H: G $? x = Some t
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- x \in tG: type_map
x: var
t: type
G': type_map
H: G' $? x = Some t
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- x \in tauto.G: type_map
x: var
t: type
G': type_map
H: G' $? x = Some t
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' $? x = Some tconstructor.G: type_map
n: nat
G': type_map
H: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- n \in Natconstructor; auto.G: type_map
e1, e2: exp
H: G |-- e1 \in Nat
H0: G |-- e2 \in Nat
IHhas_ty1: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e1 \in Nat
IHhas_ty2: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e2 \in Nat
G': type_map
H1: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- e1 ^+^ e2 \in NatG: type_map
x: var
e1: exp
t1, t2: type
H: G $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G' : type_map, (forall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> G' $? x0 = Some t) -> G' |-- e1 \in t2
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- \ x, e1 \in t1 --> t2G: type_map
x: var
e1: exp
t1, t2: type
H: G $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G' : type_map, (forall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> G' $? x0 = Some t) -> G' |-- e1 \in t2
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' $+ (x, t1) |-- e1 \in t2G: type_map
x: var
e1: exp
t1, t2: type
H: G $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G' : type_map, (forall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> G' $? x0 = Some t) -> G' |-- e1 \in t2
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tforall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> (G' $+ (x, t1)) $? x0 = Some tauto.G: type_map
x: var
e1: exp
t1, t2: type
H: G $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G' : type_map, (forall (x0 : var) (t : type), (G $+ (x, t1)) $? x0 = Some t -> G' $? x0 = Some t) -> G' |-- e1 \in t2
G': type_map
H0: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tforall (x' : var) (t' : type), G $? x' = Some t' -> G' $? x' = Some t'econstructor; auto. Qed.G: type_map
e1, e2: exp
t1, t2: type
H: G |-- e1 \in t1 --> t2
H0: G |-- e2 \in t1
IHhas_ty1: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e1 \in t1 --> t2
IHhas_ty2: forall G' : type_map, (forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some t) -> G' |-- e2 \in t1
G': type_map
H1: forall (x : var) (t : type), G $? x = Some t -> G' $? x = Some tG' |-- e1 @ e2 \in t2forall (tm : type_map) (e : exp) (t : type), ($0 |-- e \in t) -> tm |-- e \in tforall (tm : type_map) (e : exp) (t : type), ($0 |-- e \in t) -> tm |-- e \in ttm: type_map
e: exp
t: type
H: $0 |-- e \in ttm |-- e \in ttm: type_map
e: exp
t: type
H: $0 |-- e \in tforall (x : var) (t : type), $0 $? x = Some t -> tm $? x = Some ttm: type_map
e: exp
t: type
H: $0 |-- e \in t
x: var
t0: type
H0: $0 $? x = Some t0tm $? x = Some t0inversion H0. Qed.tm: type_map
e: exp
t: type
H: $0 |-- e \in t
x: var
t0: type
H0: None = Some t0tm $? x = Some t0
Replacing a variable with a properly typed term preserves typing.
forall (G : type_map) (x : var) (t' : type) (e : exp) (t : type) (e' : exp), (G $+ (x, t') |-- e \in t) -> ($0 |-- e' \in t') -> G |-- subst e' x e \in tforall (G : type_map) (x : var) (t' : type) (e : exp) (t : type) (e' : exp), (G $+ (x, t') |-- e \in t) -> ($0 |-- e' \in t') -> G |-- subst e' x e \in tG: type_map
x: var
t': type
e: exp
t: type
e': exp
H: G $+ (x, t') |-- e \in t($0 |-- e' \in t') -> G |-- subst e' x e \in tG: type_map
x: var
t': type
e: exp
t: type
e': exp
G': var -> option type
HeqG': G' = G $+ (x, t')
H: G' |-- e \in t($0 |-- e' \in t') -> G |-- subst e' x e \in tx: var
t': type
e: exp
t: type
e': exp
G': var -> option type
H: G' |-- e \in tforall G : type_map, G' = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e \in tx: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'G0 |-- if x0 ==v x then e' else x0 \in tx: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'G0 |-- \ x0, (if x0 ==v x then e1 else subst e' x e1) \in t1 --> t2x: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'G0 |-- if x0 ==v x then e' else x0 \in tx: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'
e: x0 = xG0 |-- e' \in tx: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'
n: x0 <> xG0 |-- x0 \in tx: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'
e: x0 = xG0 |-- e' \in tx: var
t': type
e': exp
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x = Some t
H0: $0 |-- e' \in t'G0 |-- e' \in tx: var
t': type
e': exp
t: type
G0: type_map
H: Some t' = Some t
H0: $0 |-- e' \in t'G0 |-- e' \in tx: var
t': type
e': exp
t: type
G0: type_map
H: Some t' = Some t
H0: $0 |-- e' \in t'
H2: t' = tG0 |-- e' \in teapply empty_weakening; eauto.x: var
e': exp
t: type
G0: type_map
H0: $0 |-- e' \in t
H: Some t = Some tG0 |-- e' \in trewrite tmap_gso in H; auto.x: var
t': type
e': exp
x0: var
t: type
G0: type_map
H: (G0 $+ (x, t')) $? x0 = Some t
H0: $0 |-- e' \in t'
n: x0 <> xG0 |-- x0 \in tx: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'G0 |-- \ x0, (if x0 ==v x then e1 else subst e' x e1) \in t1 --> t2x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
e: x0 = xG0 |-- \ x0, e1 \in t1 --> t2x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> xG0 |-- \ x0, subst e' x e1 \in t1 --> t2x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
e: x0 = xG0 |-- \ x0, e1 \in t1 --> t2x: var
t': type
e', e1: exp
t1, t2: type
G0: type_map
H: (G0 $+ (x, t')) $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H0: $0 |-- e' \in t'G0 |-- \ x, e1 \in t1 --> t2x: var
t': type
e', e1: exp
t1, t2: type
G0: type_map
H: (G0 $+ (x, t')) $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H0: $0 |-- e' \in t'G0 $+ (x, t1) |-- e1 \in t2x: var
t': type
e', e1: exp
t1, t2: type
G0: type_map
H: (G0 $+ (x, t')) $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H0: $0 |-- e' \in t'forall (x0 : var) (t : type), ((G0 $+ (x, t')) $+ (x, t1)) $? x0 = Some t -> (G0 $+ (x, t1)) $? x0 = Some tx: var
t': type
e', e1: exp
t1, t2: type
G0: type_map
H: (G0 $+ (x, t')) $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H0: $0 |-- e' \in t'
x0: var
t: type
H1: ((G0 $+ (x, t')) $+ (x, t1)) $? x0 = Some t(G0 $+ (x, t1)) $? x0 = Some tauto.x: var
t': type
e', e1: exp
t1, t2: type
G0: type_map
H: (G0 $+ (x, t')) $+ (x, t1) |-- e1 \in t2
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H0: $0 |-- e' \in t'
x0: var
t: type
H1: (G0 $+ (x, t1)) $? x0 = Some t(G0 $+ (x, t1)) $? x0 = Some tx: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> xG0 |-- \ x0, subst e' x e1 \in t1 --> t2x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> xG0 $+ (x0, t1) |-- subst e' x e1 \in t2x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> x(G0 $+ (x, t')) $+ (x0, t1) = (G0 $+ (x0, t1)) $+ (x, t')x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> xforall x1 : var, ((G0 $+ (x, t')) $+ (x0, t1)) x1 = ((G0 $+ (x0, t1)) $+ (x, t')) x1rewrite tmap_permute_ext; auto. Qed.x: var
t': type
e': exp
x0: var
e1: exp
t1, t2: type
G0: type_map
IHhas_ty: forall G : type_map, (G0 $+ (x, t')) $+ (x0, t1) = G $+ (x, t') -> ($0 |-- e' \in t') -> G |-- subst e' x e1 \in t2
H: (G0 $+ (x, t')) $+ (x0, t1) |-- e1 \in t2
H0: $0 |-- e' \in t'
n: x0 <> x
x1: var((G0 $+ (x, t')) $+ (x0, t1)) x1 = ((G0 $+ (x0, t1)) $+ (x, t')) x1
The actual preservation lemma:
forall e1 e2 : exp, step e1 e2 -> forall t : type, ($0 |-- e1 \in t) -> $0 |-- e2 \in tforall e1 e2 : exp, step e1 e2 -> forall t : type, ($0 |-- e1 \in t) -> $0 |-- e2 \in te1, e2: exp
H: step e1 e2forall t : type, ($0 |-- e1 \in t) -> $0 |-- e2 \in te1, e2: exp
H: step e1 e2
Gamma: type_map
HeqGamma: Gamma = $0forall t : type, (Gamma |-- e1 \in t) -> Gamma |-- e2 \in tx: var
e, v: exp
H: value v
t: type
H0: $0 |-- (\ x, e) @ v \in t$0 |-- subst v x e \in tn1, n2: nat
t: type
H: $0 |-- n1 ^+^ n2 \in t$0 |-- n1 + n2 \in te1, e1', e2: exp
H: step e1 e1'
IHstep: forall t : type, ($0 |-- e1 \in t) -> $0 |-- e1' \in t
t: type
H0: $0 |-- e1 @ e2 \in t$0 |-- e1' @ e2 \in tv, e2, e2': exp
H: value v
H0: step e2 e2'
IHstep: forall t : type, ($0 |-- e2 \in t) -> $0 |-- e2' \in t
t: type
H1: $0 |-- v @ e2 \in t$0 |-- v @ e2' \in te1, e1', e2: exp
H: step e1 e1'
IHstep: forall t : type, ($0 |-- e1 \in t) -> $0 |-- e1' \in t
t: type
H0: $0 |-- e1 ^+^ e2 \in t$0 |-- e1' ^+^ e2 \in tv, e2, e2': exp
H: value v
H0: step e2 e2'
IHstep: forall t : type, ($0 |-- e2 \in t) -> $0 |-- e2' \in t
t: type
H1: $0 |-- v ^+^ e2 \in t$0 |-- v ^+^ e2' \in tx: var
e, v: exp
H: value v
t: type
H0: $0 |-- (\ x, e) @ v \in t$0 |-- subst v x e \in tx: var
e, v: exp
H: value v
t: type
H0: $0 |-- (\ x, e) @ v \in t
t1: type
H4: $0 |-- \ x, e \in t1 --> t
H6: $0 |-- v \in t1$0 |-- subst v x e \in teapply substitution; eauto. Qed.x: var
e, v: exp
H: value v
t: type
H0: $0 |-- (\ x, e) @ v \in t
t1: type
H4: $0 |-- \ x, e \in t1 --> t
H6: $0 |-- v \in t1
H3: $0 $+ (x, t1) |-- e \in t$0 |-- subst v x e \in t
Multi-step Soundness
Putting progress and preservation together, we see that a well-typed term can never reach a stuck state.
Inductive trc {A:Type} {R:A -> A -> Prop}: A -> A -> Prop := | TrcRefl : forall x, trc x x | TrcFront : forall x y z (STEP: R x y) (STAR: trc y z), trc x z. Local Hint Resolve TrcRefl TrcFront : core. Notation "R ^*" := (@trc _ R) (at level 0).forall (t t' : exp) (T : type), ($0 |-- t \in T) -> (step) ^* t t' -> unstuck t'forall (t t' : exp) (T : type), ($0 |-- t \in T) -> (step) ^* t t' -> unstuck t't, t': exp
T: type
HT: $0 |-- t \in T
P: (step) ^* t t'unstuck t'T: type
x: exp
HT: $0 |-- x \in Tunstuck xT: type
x: exp
HT: $0 |-- x \in T
y, z: exp
STEP: step x y
P: (step) ^* y z
IHP: ($0 |-- y \in T) -> unstuck zunstuck zT: type
x: exp
HT: $0 |-- x \in Tunstuck xauto.T: type
x: exp
HT: unstuck xunstuck xT: type
x: exp
HT: $0 |-- x \in T
y, z: exp
STEP: step x y
P: (step) ^* y z
IHP: ($0 |-- y \in T) -> unstuck zunstuck zeapply preservation; eauto. Qed.T: type
x: exp
HT: $0 |-- x \in T
y, z: exp
STEP: step x y
P: (step) ^* y z
IHP: ($0 |-- y \in T) -> unstuck z$0 |-- y \in T
Subject Expansion
The preservation theorem is often called subject reduction, because it tells us what happens when the "subject" of the typing relation is reduced.
Another property of some type systems is called Subject Expansion.
Informally, it means that, if t
steps to t'
and $0 |-- t' \in T
, then $0 |-- t \in T
.
Is that the case for STLC?
exists (t t' : exp) (T : type), step t t' /\ ($0 |-- t' \in T) /\ ~ ($0 |-- t \in T)exists (t t' : exp) (T : type), step t t' /\ ($0 |-- t' \in T) /\ ~ ($0 |-- t \in T)exists (t' : exp) (T : type), step ((\ "x", 0) @ (\ "y", 0 @ 1)) t' /\ ($0 |-- t' \in T) /\ ~ ($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in T)exists T : type, step ((\ "x", 0) @ (\ "y", 0 @ 1)) 0 /\ ($0 |-- 0 \in T) /\ ~ ($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in T)step ((\ "x", 0) @ (\ "y", 0 @ 1)) 0 /\ ($0 |-- 0 \in Nat) /\ ~ ($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat)step ((\ "x", 0) @ (\ "y", 0 @ 1)) 0$0 |-- 0 \in Nat~ ($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat)step ((\ "x", 0) @ (\ "y", 0 @ 1)) 0constructor.value (\ "y", 0 @ 1)constructor.$0 |-- 0 \in Nat~ ($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat)($0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat) -> FalseH: $0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in NatFalseH: $0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat
t1: type
H3: $0 |-- \ "x", 0 \in t1 --> Nat
H5: $0 |-- \ "y", 0 @ 1 \in t1FalseH: $0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat
t0, t2: type
H5: $0 |-- \ "y", 0 @ 1 \in t0 --> t2
H3: $0 |-- \ "x", 0 \in (t0 --> t2) --> Nat
H6: $0 $+ ("y", t0) |-- 0 @ 1 \in t2Falseinversion H4; subst. Qed. End Stlc.H: $0 |-- (\ "x", 0) @ (\ "y", 0 @ 1) \in Nat
t0, t2: type
H5: $0 |-- \ "y", 0 @ 1 \in t0 --> t2
H3: $0 |-- \ "x", 0 \in (t0 --> t2) --> Nat
H6: $0 $+ ("y", t0) |-- 0 @ 1 \in t2
t1: type
H4: $0 $+ ("y", t0) |-- 0 \in t1 --> t2
H8: $0 $+ ("y", t0) |-- 1 \in t1False
We now go back to untyped lambda calculus without natural numbers and prove that we can encode natural numbers.
Module Ulc. Inductive exp : Set := | Var (x : var) | Abs (x : var) (body : exp) | App (e1 e2 : exp). Fixpoint subst (rep : exp) (x : var) (e : exp) : exp := match e with | Var y => if y ==v x then rep else Var y | Abs y e1 => Abs y (if y ==v x then e1 else subst rep x e1) | App e1 e2 => App (subst rep x e1) (subst rep x e2) end.
This time, for a change, we'll define big-step semantics.
Inductive eval : exp -> exp -> Prop := | BigAbs : forall x e, eval (Abs x e) (Abs x e) | BigApp : forall e1 x e1' e2 v2 v, eval e1 (Abs x e1') -> eval e2 v2 -> eval (subst v2 x e1') v -> eval (App e1 e2) v.
Note that we omit a Var
case, since variable terms can't be closed,
and therefore they aren't meaningful as top-level programs.
Inductive value : exp -> Prop := | Value : forall x e, value (Abs x e). Local Hint Constructors eval value : core.
Every value executes to itself, and eval
only produces values.
forall v : exp, value v -> eval v vforall v : exp, value v -> eval v vv: exp
H: value veval v vauto. Qed. Local Hint Resolve value_eval : core.v: exp
H: value v
x: var
e: exp
H0: Abs x e = veval (Abs x e) (Abs x e)forall e v : exp, eval e v -> value vforall e v : exp, eval e v -> value vinduction H; auto. Qed. Local Hint Resolve eval_value : core. Coercion Var : var >-> exp. Notation "\ x , e" := (Abs x e) (at level 50). Infix "@" := App (at level 49, left associativity).e, v: exp
H: eval e vvalue v
Church numerals
Here are two curious definitions.
Definition zero := \"f", \"x", "x". Definition plus1 := \"n", \"f", \"x", "f" @ ("n" @ "f" @ "x").
We can build up any natural number n
as plus1^n @ zero
. Let's prove
that, in fact, these definitions constitute a workable embedding of the
natural numbers in lambda-calculus.
A term plus^n @ zero
evaluates to something very close to what this
function returns.
Fixpoint canonical' (n : nat) : exp := match n with | O => "x" | S n' => "f" @ ((\"f", \"x", canonical' n') @ "f" @ "x") end.
This missing piece is this wrapper.
Definition canonical n := \"f", \"x", canonical' n.