StlcThe Simply Typed Lambda-Calculus
- A small subset of Coq's built-in functional language...
- ...but we'll use different informal syntax (to avoid confusion, and for consistency with standard treatments)
- variable binding
- substitution
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
Some examples:
- \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)
Note that all functions are anonymous.
The types of the STLC include Bool for boolean values and arrow types for functions.
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
What is the type of the following term?
(1) Bool → (Bool → Bool)
(2) (Bool→Bool) → Bool
(3) Bool→Bool
(4) Bool
(5) none of the above
\f:Bool→Bool. f (f tru)
How about this?
(1) Bool→ (Bool → Bool)
(2) (Bool→Bool) → Bool
(3) Bool→Bool
(4) Bool
(5) none of the above
(\f:Bool→Bool. f (f tru)) (\x:Bool. fls)
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".
Definition x := "x".
Definition y := "y".
Definition z := "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)).
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)).
Operational Semantics
- We begin by defining the set of values.
- Next, we define free variables and substitution. These are
used in the reduction rule for application expressions.
- Finally, we give the small-step relation itself.
Values
Second, an application is not a value: it represents a function being invoked on some argument, which clearly still has work left to do.
Third, for abstractions, we have a choice:
- 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.
| v_abs : ∀x T t,
value (abs x T t)
| v_tru :
value tru
| v_fls :
value fls.
STLC Programs
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.
Here are some examples:
- [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
Here is the definition, informally...
[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
... and formally:
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.
What is the result of the following substitution?
(1) (\y:T11.x (\x:T12. x))
(2) (\y:T11.s (\x:T12. s))
(3) (\y:T11.s (\x:T12. x))
(4) none of the above
[x:=s](\y:T11.x (\x:T12. x))
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' |
- first reduce t1 to a value (a function)
- then reduce the argument t2 to a value
- then reduce the application itself by substituting t2 for the bound variable x in the body t1.
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).
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).
What does the following term step to?
(1) \x:Bool. x
(2) \x:Bool→Bool. x
(3) (\x:Bool→Bool. x) (\x:Bool. x)
(4) none of the above
(\x:Bool→Bool. x) (\x:Bool. x) --> ???
What does the following term step to?
(1) \x:Bool. x
(2) \x:Bool→Bool. x
(3) (\x:Bool→Bool. x) (\x:Bool. x)
(4) (\x:Bool→Bool. x) ((\x:Bool→Bool. x) (\x:Bool. x))
(5) none of the above
(\x:Bool→Bool. x)
((\x:Bool→Bool. x) (\x:Bool. x))
--> ???
((\x:Bool→Bool. x) (\x:Bool. x))
--> ???
What does the following term normalize to?
(1) \x:Bool. x
(2) tru
(3) fls
(4) notB
(5) none of the above
(\x:Bool→Bool. x) notB tru -->* ???
where notB abbreviates \x:Bool. test x then fls else tru
What does the following term normalize to?
(1) \x:Bool. x
(2) tru
(3) fls
(4) notB tru
(5) none of the above
(\x:Bool. x) (notB tru) -->* ???
Do values and normal forms coincide in the language presented so far?
(1) yes
(2) no
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 |
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.
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.
empty ⊢ \x:Bool→B. \y:Bool→Bool. \z:Bool.
y (x z)
∈ T.
y (x z)
∈ T.
We can also show that some terms are not typable. For example, let's check that there is no typing derivation assigning a type to the term \x:Bool. \y:Bool, x y — i.e.,
¬∃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.
Another nonexample:
¬(∃S T,
empty ⊢ \x:S. x x ∈ T).
empty ⊢ \x:S. x x ∈ T).
Which of the following propositions is not provable?
(1) y:Bool ⊢ \x:Bool.x ∈ Bool→Bool
(2) ∃ T, empty ⊢ \y:Bool→Bool. \x:Bool. y x ∈ T
(3) ∃ T, empty ⊢ \y:Bool→Bool. \x:Bool. x y ∈ T
(4) ∃ S, x:S ⊢ \y:Bool→Bool. y x ∈ S
Which of these is not provable?
(1) ∃ T, empty ⊢ \y:Bool→Bool→Bool. \x:Bool. y x ∈ T
(2) ∃ S T, x:S ⊢ x x x ∈ T
(3) ∃ S U T, x:S, y:U ⊢ \z:Bool. x (y z) ∈ T
(4) ∃ S T, x:S ⊢ \y:Bool. x (x y) ∈ T