StlcThe Simply Typed Lambda-Calculus
Set Warnings "-notation-overridden,-parsing".
From PLF Require Import Maps.
From PLF Require Import Smallstep.
From PLF Require Import Maps.
From PLF Require Import Smallstep.
Overview
- variables
- function abstractions
- application
t ::= x variable
| \x:T1.t2 abstraction
| t1 t2 application
| tru constant true
| fls constant false
| test t1 then t2 else t3 conditional
| \x:T1.t2 abstraction
| t1 t2 application
| tru constant true
| fls constant false
| test t1 then t2 else t3 conditional
- \x:Bool. x
- (\x:Bool. x) tru
- \x:Bool. test x then fls else tru
- \x:Bool. tru
- \x:Bool. \y:Bool. x
- (\x:Bool. \y:Bool. x) fls tru
- \f:Bool→Bool. f (f tru)
- (\f:Bool→Bool. f (f tru)) (\x:Bool. fls)
T ::= Bool
| T → T
For example:
| T → T
- \x:Bool. fls has type Bool→Bool
- \x:Bool. x has type Bool→Bool
- (\x:Bool. x) tru has type Bool
- \x:Bool. \y:Bool. x has type Bool→Bool→Bool
(i.e., Bool → (Bool→Bool))
- (\x:Bool. \y:Bool. x) fls has type Bool→Bool
- (\x:Bool. \y:Bool. x) fls tru has type Bool
Module STLC.
Inductive ty : Type :=
| Bool : ty
| Arrow : ty → ty → ty.
Inductive tm : Type :=
| var : string → tm
| app : tm → tm → tm
| abs : string → ty → tm → tm
| tru : tm
| fls : tm
| test : tm → tm → tm → tm.
Note that an abstraction \x:T.t (formally, abs x T t) is
always annotated with the type T of its parameter, in contrast
to Coq (and other functional languages like ML, Haskell, etc.),
which use type inference to fill in missing annotations. We're
not considering type inference here.
Some examples...
Open Scope string_scope.
Definition x := "x".
Definition y := "y".
Definition z := "z".
Hint Unfold x.
Hint Unfold y.
Hint Unfold z.
Definition x := "x".
Definition y := "y".
Definition z := "z".
Hint Unfold x.
Hint Unfold y.
Hint Unfold z.
idB = \x:Bool. x
Notation idB :=
(abs x Bool (var x)).
(abs x Bool (var x)).
idBB = \x:Bool→Bool. x
Notation idBB :=
(abs x (Arrow Bool Bool) (var x)).
(abs x (Arrow Bool Bool) (var x)).
idBBBB = \x:(Bool→Bool) → (Bool→Bool). x
Notation idBBBB :=
(abs x (Arrow (Arrow Bool Bool)
(Arrow Bool Bool))
(var x)).
(abs x (Arrow (Arrow Bool Bool)
(Arrow Bool Bool))
(var x)).
k = \x:Bool. \y:Bool. x
Notation k := (abs x Bool (abs y Bool (var x))).
notB = \x:Bool. test x then fls else tru
Notation notB := (abs x Bool (test (var x) fls tru)).
(We write these as Notations rather than Definitions to make
things easier for auto.)
Operational Semantics
Values
- We can say that \x:T. t is a value only when t 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. t is always a value, no matter whether t is one or not — in other words, we can say that reduction stops at abstractions.
Compute (fun x:bool ⇒ 3 + 4)
yields:
fun x:bool ⇒ 7
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.
Inductive value : tm → Prop :=
| v_abs : ∀x T t,
value (abs x T t)
| v_tru :
value tru
| v_fls :
value fls.
Hint Constructors value.
| v_abs : ∀x T t,
value (abs x T t)
| v_tru :
value tru
| v_fls :
value fls.
Hint Constructors value.
Finally, we must consider what constitutes a complete program.
Intuitively, a "complete program" must not refer to any undefined
variables. We'll see shortly how to define the free variables
in a STLC term. A complete program is closed — that is, it
contains no free variables.
(Conversely, a term with free variables is often called an open
term.)
Having made the choice not to reduce under abstractions, we don't
need to worry about whether variables are values, since we'll
always be reducing programs "from the outside in," and that means
the step relation will always be working with closed terms.
Substitution
(\x:Bool. test x then tru else x) fls
to
test fls then tru else fls
by substituting fls for the parameter x in the body of the
function.
- [x:=tru] (test x then x else fls)
yields test tru then tru else fls
- [x:=tru] x yields tru
- [x:=tru] (test x then x else y) yields test tru then tru else y
- [x:=tru] y yields y
- [x:=tru] fls yields fls (vacuous substitution)
- [x:=tru] (\y:Bool. test y then x else fls)
yields \y:Bool. test y then tru else fls
- [x:=tru] (\y:Bool. x) yields \y:Bool. tru
- [x:=tru] (\y:Bool. y) yields \y:Bool. y
- [x:=tru] (\x:Bool. x) yields \x:Bool. x
[x:=s]x = s
[x:=s]y = y if x ≠ y
[x:=s](\x:T11. t12) = \x:T11. t12
[x:=s](\y:T11. t12) = \y:T11. [x:=s]t12 if x ≠ y
[x:=s](t1 t2) = ([x:=s]t1) ([x:=s]t2)
[x:=s]tru = tru
[x:=s]fls = fls
[x:=s](test t1 then t2 else t3) =
test [x:=s]t1 then [x:=s]t2 else [x:=s]t3
[x:=s]y = y if x ≠ y
[x:=s](\x:T11. t12) = \x:T11. t12
[x:=s](\y:T11. t12) = \y:T11. [x:=s]t12 if x ≠ y
[x:=s](t1 t2) = ([x:=s]t1) ([x:=s]t2)
[x:=s]tru = tru
[x:=s]fls = fls
[x:=s](test t1 then t2 else t3) =
test [x:=s]t1 then [x:=s]t2 else [x:=s]t3
Reserved Notation "'[' x ':=' s ']' t" (at level 20).
Fixpoint subst (x : string) (s : tm) (t : tm) : tm :=
match t with
| var x' ⇒
if eqb_string x x' then s else t
| abs x' T t1 ⇒
abs x' T (if eqb_string x x' then t1 else ([x:=s] t1))
| app t1 t2 ⇒
app ([x:=s] t1) ([x:=s] t2)
| tru ⇒
tru
| fls ⇒
fls
| test t1 t2 t3 ⇒
test ([x:=s] t1) ([x:=s] t2) ([x:=s] t3)
end
where "'[' x ':=' s ']' t" := (subst x s t).
Fixpoint subst (x : string) (s : tm) (t : tm) : tm :=
match t with
| var x' ⇒
if eqb_string x x' then s else t
| abs x' T t1 ⇒
abs x' T (if eqb_string x x' then t1 else ([x:=s] t1))
| app t1 t2 ⇒
app ([x:=s] t1) ([x:=s] t2)
| tru ⇒
tru
| fls ⇒
fls
| test t1 t2 t3 ⇒
test ([x:=s] t1) ([x:=s] t2) ([x:=s] t3)
end
where "'[' x ':=' s ']' t" := (subst x s t).
Technical note: Substitution becomes trickier to define if we
consider the case where s, the term being substituted for a
variable in some other term, may itself contain free variables.
Since we are only interested here in defining the step relation
on closed terms (i.e., terms like \x:Bool. x that include
binders for all of the variables they mention), we can sidestep this
extra complexity, but it must be dealt with when formalizing
richer languages.
For example, using the definition of substitution above to
substitute the open term s = \x:Bool. r, where r is a free
reference to some global resource, for the variable z in the
term t = \r:Bool. z, where r is a bound variable, we would get
\r:Bool. \x:Bool. r, where the free reference to r in s has
been "captured" by the binder at the beginning of t.
Why would this be bad? Because it violates the principle that the
names of bound variables do not matter. For example, if we rename
the bound variable in t, e.g., let t' = \w:Bool. z, then
[x:=s]t' is \w:Bool. \x:Bool. r, which does not behave the
same as [x:=s]t = \r:Bool. \x:Bool. r. That is, renaming a
bound variable changes how t behaves under substitution.
See, for example, [Aydemir 2008] for further discussion
of this issue.
Exercise: 3 stars, standard (substi_correct)
The definition that we gave above uses Coq's Fixpoint facility to define substitution as a function. Suppose, instead, we wanted to define substitution as an inductive relation substi. We've begun the definition by providing the Inductive header and one of the constructors; your job is to fill in the rest of the constructors and prove that the relation you've defined coincides with the function given above.
Inductive substi (s : tm) (x : string) : tm → tm → Prop :=
| s_var1 :
substi s x (var x) s
(* FILL IN HERE *)
.
Hint Constructors substi.
Theorem substi_correct : ∀s x t t',
[x:=s]t = t' ↔ substi s x t t'.
Proof.
(* FILL IN HERE *) Admitted.
☐
| s_var1 :
substi s x (var x) s
(* FILL IN HERE *)
.
Hint Constructors substi.
Theorem substi_correct : ∀s x t t',
[x:=s]t = t' ↔ substi s x t t'.
Proof.
(* FILL IN HERE *) Admitted.
Reduction
(\x:T.t12) v2 --> [x:=v2]t12
is traditionally called beta-reduction.
value v2 | (ST_AppAbs) |
(\x:T.t12) v2 --> [x:=v2]t12 |
t1 --> t1' | (ST_App1) |
t1 t2 --> t1' t2 |
value v1 | |
t2 --> t2' | (ST_App2) |
v1 t2 --> v1 t2' |
(ST_TestTru) | |
(test tru then t1 else t2) --> t1 |
(ST_TestFls) | |
(test fls then t1 else t2) --> t2 |
t1 --> t1' | (ST_Test) |
(test t1 then t2 else t3) --> (test t1' then t2 else t3) |
Reserved Notation "t1 '-->' t2" (at level 40).
Inductive step : tm → tm → Prop :=
| ST_AppAbs : ∀x T t12 v2,
value v2 →
(app (abs x T t12) v2) --> [x:=v2]t12
| ST_App1 : ∀t1 t1' t2,
t1 --> t1' →
app t1 t2 --> app t1' t2
| ST_App2 : ∀v1 t2 t2',
value v1 →
t2 --> t2' →
app v1 t2 --> app v1 t2'
| ST_TestTru : ∀t1 t2,
(test tru t1 t2) --> t1
| ST_TestFls : ∀t1 t2,
(test fls t1 t2) --> t2
| ST_Test : ∀t1 t1' t2 t3,
t1 --> t1' →
(test t1 t2 t3) --> (test t1' t2 t3)
where "t1 '-->' t2" := (step t1 t2).
Hint Constructors step.
Notation multistep := (multi step).
Notation "t1 '-->*' t2" := (multistep t1 t2) (at level 40).
Inductive step : tm → tm → Prop :=
| ST_AppAbs : ∀x T t12 v2,
value v2 →
(app (abs x T t12) v2) --> [x:=v2]t12
| ST_App1 : ∀t1 t1' t2,
t1 --> t1' →
app t1 t2 --> app t1' t2
| ST_App2 : ∀v1 t2 t2',
value v1 →
t2 --> t2' →
app v1 t2 --> app v1 t2'
| ST_TestTru : ∀t1 t2,
(test tru t1 t2) --> t1
| ST_TestFls : ∀t1 t2,
(test fls t1 t2) --> t2
| ST_Test : ∀t1 t1' t2 t3,
t1 --> t1' →
(test t1 t2 t3) --> (test t1' t2 t3)
where "t1 '-->' t2" := (step t1 t2).
Hint Constructors step.
Notation multistep := (multi step).
Notation "t1 '-->*' t2" := (multistep t1 t2) (at level 40).
Lemma step_example1 :
(app idBB idB) -->* idB.
Proof.
eapply multi_step.
apply ST_AppAbs.
apply v_abs.
simpl.
apply multi_refl. Qed.
(app idBB idB) -->* idB.
Proof.
eapply multi_step.
apply ST_AppAbs.
apply v_abs.
simpl.
apply multi_refl. Qed.
Example:
(\x:Bool→Bool. x) ((\x:Bool→Bool. x) (\x:Bool. x))
-->* \x:Bool. x
i.e.,
-->* \x:Bool. x
(idBB (idBB idB)) -->* idB.
Lemma step_example2 :
(app idBB (app idBB idB)) -->* idB.
Proof.
eapply multi_step.
apply ST_App2. auto.
apply ST_AppAbs. auto.
eapply multi_step.
apply ST_AppAbs. simpl. auto.
simpl. apply multi_refl. Qed.
(app idBB (app idBB idB)) -->* idB.
Proof.
eapply multi_step.
apply ST_App2. auto.
apply ST_AppAbs. auto.
eapply multi_step.
apply ST_AppAbs. simpl. auto.
simpl. apply multi_refl. Qed.
Example:
(\x:Bool→Bool. x)
(\x:Bool. test x then fls else tru)
tru
-->* fls
i.e.,
(\x:Bool. test x then fls else tru)
tru
-->* fls
(idBB notB) tru -->* fls.
Lemma step_example3 :
app (app idBB notB) tru -->* fls.
Proof.
eapply multi_step.
apply ST_App1. apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_TestTru. apply multi_refl. Qed.
app (app idBB notB) tru -->* fls.
Proof.
eapply multi_step.
apply ST_App1. apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_TestTru. apply multi_refl. Qed.
Example:
(\x:Bool → Bool. x)
((\x:Bool. test x then fls else tru) tru)
-->* fls
i.e.,
((\x:Bool. test x then fls else tru) tru)
-->* fls
idBB (notB tru) -->* fls.
(Note that this term doesn't actually typecheck; even so, we can
ask how it reduces.)
Lemma step_example4 :
app idBB (app notB tru) -->* fls.
Proof.
eapply multi_step.
apply ST_App2. auto.
apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_App2. auto.
apply ST_TestTru.
eapply multi_step.
apply ST_AppAbs. auto. simpl.
apply multi_refl. Qed.
app idBB (app notB tru) -->* fls.
Proof.
eapply multi_step.
apply ST_App2. auto.
apply ST_AppAbs. auto. simpl.
eapply multi_step.
apply ST_App2. auto.
apply ST_TestTru.
eapply multi_step.
apply ST_AppAbs. auto. simpl.
apply multi_refl. Qed.
We can use the normalize tactic defined in the Smallstep chapter
to simplify these proofs.
Lemma step_example1' :
app idBB idB -->* idB.
Proof. normalize. Qed.
Lemma step_example2' :
app idBB (app idBB idB) -->* idB.
Proof. normalize. Qed.
Lemma step_example3' :
app (app idBB notB) tru -->* fls.
Proof. normalize. Qed.
Lemma step_example4' :
app idBB (app notB tru) -->* fls.
Proof. normalize. Qed.
app idBB idB -->* idB.
Proof. normalize. Qed.
Lemma step_example2' :
app idBB (app idBB idB) -->* idB.
Proof. normalize. Qed.
Lemma step_example3' :
app (app idBB notB) tru -->* fls.
Proof. normalize. Qed.
Lemma step_example4' :
app idBB (app notB tru) -->* fls.
Proof. normalize. Qed.
Lemma step_example5 :
app (app idBBBB idBB) idB
-->* idB.
Proof.
(* FILL IN HERE *) Admitted.
Lemma step_example5_with_normalize :
app (app idBBBB idBB) idB
-->* idB.
Proof.
(* FILL IN HERE *) Admitted.
☐
app (app idBBBB idBB) idB
-->* idB.
Proof.
(* FILL IN HERE *) Admitted.
Lemma step_example5_with_normalize :
app (app idBBBB idBB) idB
-->* idB.
Proof.
(* FILL IN HERE *) Admitted.
Contexts
Definition context := partial_map ty.
Typing Relation
Gamma x = T | (T_Var) |
Gamma ⊢ x ∈ T |
(x ⊢> T11 ; Gamma) ⊢ t12 ∈ T12 | (T_Abs) |
Gamma ⊢ \x:T11.t12 ∈ T11->T12 |
Gamma ⊢ t1 ∈ T11->T12 | |
Gamma ⊢ t2 ∈ T11 | (T_App) |
Gamma ⊢ t1 t2 ∈ T12 |
(T_Tru) | |
Gamma ⊢ tru ∈ Bool |
(T_Fls) | |
Gamma ⊢ fls ∈ Bool |
Gamma ⊢ t1 ∈ Bool Gamma ⊢ t2 ∈ T Gamma ⊢ t3 ∈ T | (T_Test) |
Gamma ⊢ test t1 then t2 else t3 ∈ T |
Reserved Notation "Gamma '⊢' t '∈' T" (at level 40).
Inductive has_type : context → tm → ty → Prop :=
| T_Var : ∀Gamma x T,
Gamma x = Some T →
Gamma ⊢ var x ∈ T
| T_Abs : ∀Gamma x T11 T12 t12,
(x ⊢> T11 ; Gamma) ⊢ t12 ∈ T12 →
Gamma ⊢ abs x T11 t12 ∈ Arrow T11 T12
| T_App : ∀T11 T12 Gamma t1 t2,
Gamma ⊢ t1 ∈ Arrow T11 T12 →
Gamma ⊢ t2 ∈ T11 →
Gamma ⊢ app t1 t2 ∈ T12
| T_Tru : ∀Gamma,
Gamma ⊢ tru ∈ Bool
| T_Fls : ∀Gamma,
Gamma ⊢ fls ∈ Bool
| T_Test : ∀t1 t2 t3 T Gamma,
Gamma ⊢ t1 ∈ Bool →
Gamma ⊢ t2 ∈ T →
Gamma ⊢ t3 ∈ T →
Gamma ⊢ test t1 t2 t3 ∈ T
where "Gamma '⊢' t '∈' T" := (has_type Gamma t T).
Hint Constructors has_type.
Inductive has_type : context → tm → ty → Prop :=
| T_Var : ∀Gamma x T,
Gamma x = Some T →
Gamma ⊢ var x ∈ T
| T_Abs : ∀Gamma x T11 T12 t12,
(x ⊢> T11 ; Gamma) ⊢ t12 ∈ T12 →
Gamma ⊢ abs x T11 t12 ∈ Arrow T11 T12
| T_App : ∀T11 T12 Gamma t1 t2,
Gamma ⊢ t1 ∈ Arrow T11 T12 →
Gamma ⊢ t2 ∈ T11 →
Gamma ⊢ app t1 t2 ∈ T12
| T_Tru : ∀Gamma,
Gamma ⊢ tru ∈ Bool
| T_Fls : ∀Gamma,
Gamma ⊢ fls ∈ Bool
| T_Test : ∀t1 t2 t3 T Gamma,
Gamma ⊢ t1 ∈ Bool →
Gamma ⊢ t2 ∈ T →
Gamma ⊢ t3 ∈ T →
Gamma ⊢ test t1 t2 t3 ∈ T
where "Gamma '⊢' t '∈' T" := (has_type Gamma t T).
Hint Constructors has_type.
Example typing_example_1 :
empty ⊢ abs x Bool (var x) ∈ Arrow Bool Bool.
Proof.
apply T_Abs. apply T_Var. reflexivity. Qed.
Note that, since we added the has_type constructors to the hints
database, auto can actually solve this one immediately.
Example typing_example_1' :
empty ⊢ abs x Bool (var x) ∈ Arrow Bool Bool.
Proof. auto. Qed.
empty ⊢ abs x Bool (var x) ∈ Arrow Bool Bool.
Proof. auto. Qed.
More examples:
empty ⊢ \x:A. \y:A→A. y (y x)
∈ A → (A→A) → A.
∈ A → (A→A) → A.
Example typing_example_2 :
empty ⊢
(abs x Bool
(abs y (Arrow Bool Bool)
(app (var y) (app (var y) (var x))))) ∈
(Arrow Bool (Arrow (Arrow Bool Bool) Bool)).
Proof with auto using update_eq.
apply T_Abs.
apply T_Abs.
eapply T_App. apply T_Var...
eapply T_App. apply T_Var...
apply T_Var...
Qed.
Exercise: 2 stars, standard, optional (typing_example_2_full)
Prove the same result without using auto, eauto, or eapply (or ...).
Example typing_example_2_full :
empty ⊢
(abs x Bool
(abs y (Arrow Bool Bool)
(app (var y) (app (var y) (var x))))) ∈
(Arrow Bool (Arrow (Arrow Bool Bool) Bool)).
Proof.
(* FILL IN HERE *) Admitted.
☐
empty ⊢
(abs x Bool
(abs y (Arrow Bool Bool)
(app (var y) (app (var y) (var x))))) ∈
(Arrow Bool (Arrow (Arrow Bool Bool) Bool)).
Proof.
(* FILL IN HERE *) Admitted.
Exercise: 2 stars, standard (typing_example_3)
Formally prove the following typing derivation holds:
empty ⊢ \x:Bool→B. \y:Bool→Bool. \z:Bool.
y (x z)
∈ T.
y (x z)
∈ T.
Example typing_example_3 :
∃T,
empty ⊢
(abs x (Arrow Bool Bool)
(abs y (Arrow Bool Bool)
(abs z Bool
(app (var y) (app (var x) (var z)))))) ∈
T.
Proof with auto.
(* FILL IN HERE *) Admitted.
☐
∃T,
empty ⊢
(abs x (Arrow Bool Bool)
(abs y (Arrow Bool Bool)
(abs z Bool
(app (var y) (app (var x) (var z)))))) ∈
T.
Proof with auto.
(* FILL IN HERE *) Admitted.
¬∃T,
empty ⊢ \x:Bool. \y:Bool, x y ∈ T.
empty ⊢ \x:Bool. \y:Bool, x y ∈ T.
Example typing_nonexample_1 :
¬∃T,
empty ⊢
(abs x Bool
(abs y Bool
(app (var x) (var y)))) ∈
T.
Proof.
intros Hc. inversion Hc.
(* The clear tactic is useful here for tidying away bits of
the context that we're not going to need again. *)
inversion H. subst. clear H.
inversion H5. subst. clear H5.
inversion H4. subst. clear H4.
inversion H2. subst. clear H2.
inversion H5. subst. clear H5.
inversion H1. Qed.
Exercise: 3 stars, standard, optional (typing_nonexample_3)
Another nonexample:
¬(∃S T,
empty ⊢ \x:S. x x ∈ T).
empty ⊢ \x:S. x x ∈ T).
Example typing_nonexample_3 :
¬(∃S T,
empty ⊢
(abs x S
(app (var x) (var x))) ∈
T).
Proof.
(* FILL IN HERE *) Admitted.
☐
¬(∃S T,
empty ⊢
(abs x S
(app (var x) (var x))) ∈
T).
Proof.
(* FILL IN HERE *) Admitted.
End STLC.
(* Sat 22 Dec 2018 10:17:48 EST *)
(* Sat 22 Dec 2018 10:17:48 EST *)