StlcNew first draft

This commit is contained in:
wadler 2018-05-19 19:05:03 -03:00
parent bcd2172337
commit 6252085f2d
4 changed files with 1777 additions and 4 deletions

910
src/StlcNew.lagda Normal file
View file

@ -0,0 +1,910 @@
---
title : "StlcNew: The Simply Typed Lambda-Calculus"
layout : page
permalink : /StlcNew
---
The _lambda-calculus_, first published by the logician Alonzo Church in
1932, is a core calculus with only three syntactic constructs:
variables, abstraction, and application. It embodies the concept of
_functional abstraction_, which shows up in almost every programming
language in some form (as functions, procedures, or methods).
The _simply-typed lambda calculus_ (or STLC) is a variant of the
lambda calculus published by Church in 1940. It has just the three
constructs above for function types, plus whatever else is required
for base types. Church had a minimal base type with no operations;
we will be slightly more pragmatic and choose booleans as our base type.
This chapter formalises the simply-typed lambda calculus, giving its
syntax, small-step semantics, and typing rules.
The [next chapter](StlcNewProp) reviews its main properties,
including progress and preservation.
The most challenging new concepts will be
_variable binding_ and _substitution_.
We choose booleans as our base type for simplicity. In later
chapters we'll enrich lambda calculus with types including naturals,
products, sums, and lists.
## Imports
\begin{code}
module StlcNew where
open import Data.String using (String; _≟_)
open import Data.Empty using (⊥; ⊥-elim)
open import Relation.Nullary using (Dec; yes; no; ¬_)
open import Relation.Binary.PropositionalEquality using (_≡_; _≢_; refl)
\end{code}
## Syntax
We have just two types.
* Functions, `A ⇒ B`
* Booleans, `𝔹`
We require some form of base type, because otherwise the set of types
would be empty. Church used a trivial base type `o` with no operations.
For us, it is more convenient to use booleans. Later we will consider
numbers as a base type.
Here is the syntax of types in BNF.
A, B, C ::= A ⇒ B | 𝔹
And here it is formalised in Agda.
\begin{code}
infixr 20 _⇒_
data Type : Set where
_⇒_ : Type → Type → Type
𝔹 : Type
\end{code}
Terms have six constructs. Three are for the core lambda calculus:
* Variables, `# x`
* Abstractions, `ƛ x ⇒ N`
* Applications, `L · M`
and three are for the base type, booleans:
* True, `true`
* False, `false`
* Conditions, `if L then M else N`
Abstraction is also called lambda abstraction, and is the construct
from which the calculus takes its name.
With the exception of variables, each term form either constructs
a value of a given type (abstractions yield functions, true and
false yield booleans) or deconstructs it (applications use functions,
conditionals use booleans). We will see this again when we come
to the rules for assigning types to terms, where constructors
correspond to introduction rules and deconstructors to eliminators.
Here is the syntax of terms in BNF.
L, M, N ::= # x | λ x ⇒ N | L · M | true | false | if L then M else N
And here it is formalised in Agda.
\begin{code}
infix 30 #_
infixl 20 _·_
infix 15 ƛ_⇒_
infix 15 if_then_else_
Id : Set
Id = String
data Term : Set where
#_ : Id → Term
ƛ_⇒_ : Id → Term → Term
_·_ : Term → Term → Term
true : Term
false : Term
if_then_else_ : Term → Term → Term → Term
\end{code}
### Formal vs informal
In informal presentation of formal semantics, one uses choice of
variable name to disambiguate and writes `x` rather than `# x`
for a term that is a variable. Agda requires we distinguish.
Often researchers use `var x` rather than `# x`, but we chose
the latter since it is less noisy.
Similarly, informal presentation often use the same notation for
function types, lambda abstraction, and function application in
both the object language (the language one is describing) and the
meta-language (the language in which the description is written),
trusting readers can use context to distinguish the two. Agda is
is not quite so forgiving, so here we use `A ⇒ B`, `ƛ x ⇒ N`,
and `L · M` for the object language, as compared to `A → B`, `λ x → N`,
and `L M` in our meta-language, Agda.
### Examples
Here are a couple of example terms, `not` of type
`𝔹𝔹`, which complements its argument, and `two` of type
`(𝔹𝔹) ⇒ 𝔹𝔹` which takes a function and a boolean
and applies the function to the boolean twice.
\begin{code}
not two : Term
not = ƛ "x" ⇒ if # "x" then false else true
two = ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x")
\end{code}
### Bound and free variables
In an abstraction `ƛ x ⇒ N` we call `x` the _bound_ variable
and `N` the _body_ of the abstraction. One of the most important
aspects of lambda calculus is that names of bound variables are
irrelevant. Thus the five terms
* `` ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x") ``
* `` ƛ "g" ⇒ ƛ "y" ⇒ # "g" · (# "g" · # "y") ``
* `` ƛ "fred" ⇒ ƛ "xander" ⇒ # "fred" · (# "fred" · # "xander") ``
* `` λ[ 😇 𝔹𝔹 ] λ[ 😈 𝔹 ] ` 😇 · (` 😇 · ` 😈 ) ``
* `` ƛ "x" ⇒ ƛ "f" ⇒ # "x" · (# "x" · # "f") ``
are all considered equivalent. This equivalence relation
is often called _alpha renaming_.
As we descend from a term into its subterms, variables
that are bound may become free. Consider the following terms.
* `` ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x") ``
Both variable `f` and `x` are bound.
* `` ƛ "x" ⇒ # "f" · (# "f" · # "x") ``
has `x` as a bound variable but `f` as a free variable.
* `` # "f" · (# "f" · # "x") ``
has both `f` and `x` as free variables.
We say that a term with no free variables is _closed_; otherwise it is
_open_. Of the three terms above, the first is closed and the other
two are open. A formal definition of bound and free variables will be
given in the next chapter.
Different occurrences of a variable may be bound and free.
In the term
(ƛ "x" ⇒ # "x") · # "x"
the inner occurrence of `x` is bound while the outer occurrence is free.
Note that by alpha renaming, the term above is equivalent to
(ƛ "y" ⇒ # "y") · # "x"
in which `y` is bound and `x` is free. A common convention, called the
Barendregt convention, is to use alpha renaming to ensure that the bound
variables in a term are distinct from the free variables, which can
avoid confusions that may arise if bound and free variables have the
same names.
### Precedence
As in Agda, functions of two or more arguments are represented via
currying. This is made more convenient by declaring `_⇒_` to
associate to the right and `_·_` to associate to the left.
Thus,
* `(𝔹𝔹) ⇒ 𝔹𝔹` abbreviates `(𝔹𝔹) ⇒ (𝔹𝔹)`
* `two · not · true` abbreviates `(two · not) · true`.
We choose the binding strength for abstractions and conditionals
to be weaker than application. For instance,
* `` ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x") ``
- denotes `` (ƛ "f" ⇒ (ƛ "x" ⇒ (# "f" · (# "f" · # "x")))) ``
- and not `` (ƛ "f" ⇒ (ƛ "x" ⇒ # "f")) · (# "f" · # "x") ``
\begin{code}
_ : (𝔹𝔹) ⇒ 𝔹𝔹 ≡ (𝔹𝔹) ⇒ (𝔹𝔹)
_ = refl
_ : two · not · true ≡ (two · not) · true
_ = refl
_ : ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x")
≡ (ƛ "f" ⇒ (ƛ "x" ⇒ (# "f" · (# "f" · # "x"))))
_ = refl
\end{code}
### Quiz
* What is the type of the following term?
ƛ "f" ⇒ # "f" · (# "f" · true)
1. `𝔹 ⇒ (𝔹𝔹)`
2. `(𝔹𝔹) ⇒ 𝔹`
3. `𝔹𝔹𝔹`
4. `𝔹𝔹`
5. `𝔹`
Give more than one answer if appropriate.
* What is the type of the following term?
(ƛ "f" ⇒ # "f" · (# "f" · true)) · not
1. `𝔹 ⇒ (𝔹𝔹)`
2. `(𝔹𝔹) ⇒ 𝔹`
3. `𝔹𝔹𝔹`
4. `𝔹𝔹`
5. `𝔹`
Give more than one answer if appropriate.
## Values
We only consider reduction of _closed_ terms,
those that contain no free variables. We consider
a precise definition of free variables in
[StlcPropNew](StlcPropNew).
A term is a value if it is fully reduced.
For booleans, the situation is clear: `true` and
`false` are values, while conditionals are not.
For functions, applications are not values, because
we expect them to further reduce, and variables are
not values, because we focus on closed terms.
Following convention, we treat all abstractions
as values.
The predicate `Value M` holds if term `M` is a value.
\begin{code}
data Value : Term → Set where
value-λ : ∀ {x N} → Value (ƛ x ⇒ N)
value-true : Value true
value-false : Value false
\end{code}
We let `V` and `W` range over values.
### Formal vs informal
In informal presentations of formal semantics, using
`V` as the name of a metavariable is sufficient to
indicate that it is a value. In Agda, we must explicitly
invoke the `Value` predicate.
### Other approaches
An alternative is not to focus on closed terms,
to treat variables as values, and to treat
`ƛ x ⇒ N` as a value only if `N` is a value.
Indeed, this is how Agda normalises terms.
We consider this approach in a [later chapter](Untyped).
## Substitution
The heart of lambda calculus is the operation of
substituting one term for a variable in another term.
Substitution plays a key role in defining the
operational semantics of function application.
For instance, we have
(ƛ "f" ⇒ # "f" · (# "f" · true)) · not
not · (not · true)
where we substitute `not` for `` `f `` in the body
of the function abstraction.
We write substitution as `N [ x := V ]`, meaning
"substitute term `V` for free occurrences of variable `x` in term `N`",
or, more compactly, "substitute `V` for `x` in `N`".
Substitution works if `V` is any closed term;
it need not be a value, but we use `V` since we
always substitute values.
Here are some examples.
* `` # "f" [ "f" := not ] `` yields `` not ``
* `` true [ "f" := not ] `` yields `` true ``
* `` (# "f" · true) [ "f" := not ] `` yields `` not · true ``
* `` (# "f" · (# "f" · true)) [ "f" := not ] `` yields `` not · (not · true) ``
* `` (ƛ "x" ⇒ # "f" · (# "f" · # "x")) [ "f" := not ] `` yields `` ƛ "x" ⇒ not · (not · # "x") ``
* `` (ƛ "y" ⇒ # "y") [ "x" := true ] `` yields `` ƛ "y" ⇒ # "y" ``
* `` (ƛ "x" ⇒ # "x") [ "x" := true ] `` yields `` ƛ "x" ⇒ # "x" ``
The last example is important: substituting `true` for `x` in
`` ƛ "x" ⇒ # "x" `` does _not_ yield `` ƛ "x" ⇒ true ``.
The reason for this is that `x` in the body of `` ƛ "x" ⇒ # "x" ``
is _bound_ by the abstraction. An important feature of abstraction
is that the choice of bound names is irrelevant: both
`` ƛ "x" ⇒ # "x" `` and `` ƛ "y" ⇒ # "y" `` stand for the
identity function. The way to think of this is that `x` within
the body of the abstraction stands for a _different_ variable than
`x` outside the abstraction, they both just happen to have the same
name.
Here is the formal definition in Agda.
\begin{code}
infix 25 _[_:=_]
_[_:=_] : Term → Id → Term → Term
(# x) [ w := V ] with w ≟ x
... | yes _ = V
... | no _ = # x
(ƛ x ⇒ N) [ w := V ] with w ≟ x
... | yes _ = ƛ x ⇒ N
... | no _ = ƛ x ⇒ (N [ w := V ])
(L · M) [ w := V ] = (L [ w := V ]) · (M [ w := V ])
(true) [ w := V ] = true
(false) [ w := V ] = false
(if L then M else N) [ w := V ] =
if L [ w := V ] then M [ w := V ] else N [ w := V ]
\end{code}
The two key cases are variables and abstraction.
* For variables, we compare `w`, the variable we are substituting for,
with `x`, the variable in the term. If they are the same,
we yield `V`, otherwise we yield `x` unchanged.
* For abstractions, we compare `w`, the variable we are substituting for,
with `x`, the variable bound in the abstraction. If they are the same,
we yield the abstraction unchanged, otherwise we subsititute inside the body.
In all other cases, we push substitution recursively into
the subterms.
#### Examples
Here is confirmation that the examples above are correct.
\begin{code}
_ : # "f" [ "f" := not ] ≡ not
_ = refl
_ : true [ "f" := not ] ≡ true
_ = refl
_ : (# "f" · true) [ "f" := not ] ≡ not · true
_ = refl
_ : (# "f" · (# "f" · true)) [ "f" := not ] ≡ not · (not · true)
_ = refl
_ : (ƛ "x" ⇒ # "f" · (# "f" · # "x")) [ "f" := not ] ≡ ƛ "x" ⇒ not · (not · # "x")
_ = refl
_ : (ƛ "y" ⇒ # "y") [ "x" := true ] ≡ ƛ "y" ⇒ # "y"
_ = refl
_ : (ƛ "x" ⇒ # "x") [ "x" := true ] ≡ ƛ "x" ⇒ # "x"
_ = refl
\end{code}
#### Quiz
What is the result of the following substitution?
(ƛ "y" ⇒ # "x" · (ƛ "x" ⇒ # "x")) [ "x" := true ]
1. `` (ƛ "y" ⇒ # "x" · (ƛ "x" ⇒ # "x")) ``
2. `` (ƛ "y" ⇒ # "x" · (ƛ "x" ⇒ true)) ``
3. `` (ƛ "y" ⇒ true · (ƛ "x" ⇒ # "x")) ``
4. `` (ƛ "y" ⇒ true · (ƛ "x" ⇒ true)) ``
## Reduction
We give the reduction rules for call-by-value lambda calculus. To
reduce an application, first we reduce the left-hand side until it
becomes a value (which must be an abstraction); then we reduce the
right-hand side until it becomes a value; and finally we substitute
the argument for the variable in the abstraction. To reduce a
conditional, we first reduce the condition until it becomes a value;
if the condition is true the conditional reduces to the first
branch and if false it reduces to the second branch.
In an informal presentation of the formal semantics,
the rules for reduction are written as follows.
L ⟹ L
--------------- ξ·₁
L · M ⟹ L · M
M ⟹ M
--------------- ξ·₂
V · M ⟹ V · M
--------------------------------- βλ·
(ƛ x ⇒ N) · V ⟹ N [ x := V ]
L ⟹ L
----------------------------------------- ξif
if L then M else N ⟹ if L then M else N
-------------------------- βif-true
if true then M else N ⟹ M
--------------------------- βif-false
if false then M else N ⟹ N
As we will show later, the rules are deterministic, in that
at most one rule applies to every term. As we will also show
later, for every well-typed term either a reduction applies
or it is a value.
The rules break into two sorts. Compatibility rules
direct us to reduce some part of a term.
We give them names starting with the Greek letter xi, `ξ`.
Once a term is sufficiently
reduced, it will consist of a constructor and
a deconstructor, in our case `λ` and `·`, or
`if` and `true`, or `if` and `false`.
We give them names starting with the Greek letter beta, `β`,
and indeed such rules are traditionally called beta rules.
Here are the above rules formalised in Agda.
\begin{code}
infix 10 _⟹_
data _⟹_ : Term → Term → Set where
ξ·₁ : ∀ {L L M}
→ L ⟹ L
-----------------
→ L · M ⟹ L · M
ξ·₂ : ∀ {V M M}
→ Value V
→ M ⟹ M
-----------------
→ V · M ⟹ V · M
βλ· : ∀ {x N V}
→ Value V
------------------------------------
→ (ƛ x ⇒ N) · V ⟹ N [ x := V ]
ξif : ∀ {L L M N}
→ L ⟹ L
-------------------------------------------
→ if L then M else N ⟹ if L then M else N
βif-true : ∀ {M N}
----------------------------
→ if true then M else N ⟹ M
βif-false : ∀ {M N}
-----------------------------
→ if false then M else N ⟹ N
\end{code}
#### Quiz
What does the following term step to?
(ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ⟹ ???
1. `` (ƛ "x" ⇒ # "x") ``
2. `` (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ``
3. `` (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ``
What does the following term step to?
(ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ⟹ ???
1. `` (ƛ "x" ⇒ # "x") ``
2. `` (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ``
3. `` (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") · (ƛ "x" ⇒ # "x") ``
What does the following term step to? (Where `not` is as defined above.)
not · true ⟹ ???
1. `` if # "x" then false else true ``
2. `` if true then false else true ``
3. `` true ``
4. `` false ``
What does the following term step to? (Where `two` and `not` are as defined above.)
two · not · true ⟹ ???
1. `` not · (not · true) ``
2. `` (ƛ "x" ⇒ not · (not · # "x")) · true ``
3. `` true ``
4. `` false ``
## Reflexive and transitive closure
A single step is only part of the story. In general, we wish to repeatedly
step a closed term until it reduces to a value. We do this by defining
the reflexive and transitive closure `⟹*` of the step function `⟹`.
In an informal presentation of the formal semantics, the rules
are written as follows.
------- done
M ⟹* M
L ⟹ M
M ⟹* N
------- step
L ⟹* N
Here it is formalised in Agda, along similar lines to what
we used for reasoning about [Equality](Equality).
\begin{code}
infix 2 _⟹*_
infix 1 begin_
infixr 2 _⟹⟨_⟩_
infix 3 _∎
data _⟹*_ : Term → Term → Set where
_∎ : ∀ M
---------
→ M ⟹* M
_⟹⟨_⟩_ : ∀ L {M N}
→ L ⟹ M
→ M ⟹* N
---------
→ L ⟹* N
begin_ : ∀ {M N} → (M ⟹* N) → (M ⟹* N)
begin M⟹*N = M⟹*N
\end{code}
We can read this as follows.
* From term `M`, we can take no steps, giving `M ∎` of type `M ⟹* M`.
* From term `L` we can take a single step `L⟹M` of type `L ⟹ M`
followed by zero or more steps `M⟹*N` of type `M ⟹* N`,
giving `L ⟨ L⟹M ⟩ M⟹*N` of type `L ⟹* N`.
The names have been chosen to allow us to lay
out example reductions in an appealing way.
\begin{code}
_ : not · true ⟹* false
_ =
begin
not · true
⟹⟨ βλ· value-true ⟩
if true then false else true
⟹⟨ βif-true ⟩
false
_ : two · not · true ⟹* true
_ =
begin
two · not · true
⟹⟨ ξ·₁ (βλ· value-λ) ⟩
(ƛ "x" ⇒ not · (not · # "x")) · true
⟹⟨ βλ· value-true ⟩
not · (not · true)
⟹⟨ ξ·₂ value-λ (βλ· value-true) ⟩
not · (if true then false else true)
⟹⟨ ξ·₂ value-λ βif-true ⟩
not · false
⟹⟨ βλ· value-false ⟩
if false then false else true
⟹⟨ βif-false ⟩
true
\end{code}
Much of the above, though not all, can be filled in using C-c C-r and C-c C-s.
## Typing
While reduction considers only closed terms, typing must
consider terms with free variables. To type a term,
we must first type its subterms, and in particular in the
body of an abstraction its bound variable may appear free.
In general, we use typing _judgements_ of the form
Γ ⊢ M ⦂ A
to assert in type environment `Γ` that term `M` has type `A`.
Environment `Γ` provides types for all the free variables in `M`.
Here are three examples.
* `` ∅ , "f" ⦂ 𝔹𝔹 , "x" ⦂ 𝔹 ⊢ # "f" · (# "f" · # "x") ⦂ 𝔹 ``
* `` ∅ , "f" ⦂ 𝔹𝔹 ⊢ (ƛ "x" ⇒ # "f" · (# "f" · # "x")) ⦂ 𝔹𝔹 ``
* `` ∅ ⊢ ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x")) ⦂ (𝔹𝔹) ⇒ 𝔹𝔹 ``
Environments are partial maps from identifiers to types, built using `∅`
for the empty map, and `Γ , x ⦂ A` for the map that extends
environment `Γ` by mapping variable `x` to type `A`.
*It's redundant to have two versions of the rules*
*Need text to explain `Γ ∋ x ⦂ A`*
In an informal presentation of the formal semantics,
the rules for typing are written as follows.
Γ x ≡ A
----------- Ax
Γ ⊢ ` x ⦂ A
Γ , x ⦂ A ⊢ N ⦂ B
------------------------ ⇒-I
Γ ⊢ ƛ x ⇒ N ⦂ A ⇒ B
Γ ⊢ L ⦂ A ⇒ B
Γ ⊢ M ⦂ A
-------------- ⇒-E
Γ ⊢ L · M ⦂ B
------------- 𝔹-I₁
Γ ⊢ true ⦂ 𝔹
-------------- 𝔹-I₂
Γ ⊢ false ⦂ 𝔹
Γ ⊢ L : 𝔹
Γ ⊢ M ⦂ A
Γ ⊢ N ⦂ A
-------------------------- 𝔹-E
Γ ⊢ if L then M else N ⦂ A
As we will show later, the rules are deterministic, in that
at most one rule applies to every term.
The proof rules come in pairs, with rules to introduce and to
eliminate each connective, labeled `-I` and `-E`, respectively. As we
read the rules from top to bottom, introduction and elimination rules
do what they say on the tin: the first _introduces_ a formula for the
connective, which appears in the conclusion but not in the premises;
while the second _eliminates_ a formula for the connective, which appears in
a premise but not in the conclusion. An introduction rule describes
how to construct a value of the type (abstractions yield functions,
true and false yield booleans), while an elimination rule describes
how to deconstruct a value of the given type (applications use
functions, conditionals use booleans).
Here are the above rules formalised in Agda.
\begin{code}
infix 4 _∋_⦂_
infix 4 _⊢_⦂_
infixl 5 _,_⦂_
data Context : Set where
∅ : Context
_,_⦂_ : Context → Id → Type → Context
data _∋_⦂_ : Context → Id → Type → Set where
Z : ∀ {Γ x A}
------------------
→ Γ , x ⦂ A ∋ x ⦂ A
S : ∀ {Γ w x A B}
→ w ≢ x
→ Γ ∋ w ⦂ B
------------------
→ Γ , x ⦂ A ∋ w ⦂ B
data _⊢_⦂_ : Context → Term → Type → Set where
Ax : ∀ {Γ x A}
→ Γ ∋ x ⦂ A
-------------
→ Γ ⊢ # x ⦂ A
⇒-I : ∀ {Γ x N A B}
→ Γ , x ⦂ A ⊢ N ⦂ B
--------------------
→ Γ ⊢ ƛ x ⇒ N ⦂ A ⇒ B
⇒-E : ∀ {Γ L M A B}
→ Γ ⊢ L ⦂ A ⇒ B
→ Γ ⊢ M ⦂ A
--------------
→ Γ ⊢ L · M ⦂ B
𝔹-I₁ : ∀ {Γ}
-------------
→ Γ ⊢ true ⦂ 𝔹
𝔹-I₂ : ∀ {Γ}
--------------
→ Γ ⊢ false ⦂ 𝔹
𝔹-E : ∀ {Γ L M N A}
→ Γ ⊢ L ⦂ 𝔹
→ Γ ⊢ M ⦂ A
→ Γ ⊢ N ⦂ A
---------------------------
→ Γ ⊢ if L then M else N ⦂ A
\end{code}
#### Example type derivations
Here are a couple of typing examples. First, here is how
they would be written in an informal description of the
formal semantics.
Derivation of `not`:
------------ Z
Γ₀ ∋ "x" ⦂ B
-------------- Ax -------------- 𝔹-I₂ ------------- 𝔹-I₁
Γ₀ ⊢ # "x" ⦂ 𝔹 Γ₀ ⊢ false ⦂ 𝔹 Γ₀ ⊢ true ⦂ 𝔹
------------------------------------------------------ 𝔹-E
Γ₀ ⊢ if # "x" then false else true ⦂ 𝔹
--------------------------------------------------- ⇒-I
∅ ⊢ ƛ "x" ⇒ if # "x" then false else true ⦂ 𝔹𝔹
where `Γ₀ = ∅ , x ⦂ 𝔹`.
Derivation of `two`:
∋f ∋f ∋x
------------------- Ax ------------------- Ax --------------- Ax
Γ₂ ⊢ # "f" ⦂ 𝔹𝔹 Γ₂ ⊢ # "f" ⦂ 𝔹𝔹 Γ₂ ⊢ # "x" ⦂ 𝔹
------------------- Ax ------------------------------------------ ⇒-E
Γ₂ ⊢ # "f" ⦂ 𝔹𝔹 Γ₂ ⊢ # "f" · # "x" ⦂ 𝔹
-------------------------------------------------- ⇒-E
Γ₂ ⊢ # "f" · (# "f" · # "x") ⦂ 𝔹
---------------------------------------------- ⇒-I
Γ₁ ⊢ ƛ "x" ⇒ # "f" · (# "f" · # "x") ⦂ 𝔹𝔹
---------------------------------------------------------- ⇒-I
∅ ⊢ ƛ "f" ⇒ ƛ "x" ⇒ # "f" · (# "f" · # "x") ⦂ 𝔹𝔹
Where `∋f` and `∋x` abbreviate the two derivations:
---------------- Z
"f" ≢ "x" Γ₁ ∋ "f" ⦂ B ⇒ B
----------------------------- S ------------- Z
Γ₂ ∋ "f" ⦂ B ⇒ B Γ₂ ∋ "x" ⦂ 𝔹
where `Γ₁ = ∅ , f ⦂ 𝔹𝔹` and `Γ₂ = ∅ , f ⦂ 𝔹𝔹 , x ⦂ 𝔹`.
Here are the above derivations formalised in Agda.
\begin{code}
_≠_ : ∀ (x y : Id) → x ≢ y
x ≠ y with x ≟ y
... | no x≢y = x≢y
... | yes _ = ⊥-elim impossible
where postulate impossible : ⊥
⊢not : ∅ ⊢ not ⦂ 𝔹𝔹
⊢not = ⇒-I (𝔹-E (Ax Z) 𝔹-I₂ 𝔹-I₁)
⊢two : ∅ ⊢ two ⦂ (𝔹𝔹) ⇒ 𝔹𝔹
⊢two = ⇒-I (⇒-I (⇒-E (Ax ⊢f) (⇒-E (Ax ⊢f) (Ax ⊢x))))
where
⊢f = S ("f" ≠ "x") Z
⊢x = Z
\end{code}
#### Interaction with Agda
Construction of a type derivation is best done interactively.
Start with the declaration:
⊢not : ∅ ⊢ not ⦂ 𝔹𝔹
⊢not = ?
Typing C-l causes Agda to create a hole and tell us its expected type.
⊢not = { }0
?0 : ∅ ⊢ not ⦂ 𝔹𝔹
Now we fill in the hole by typing C-c C-r. Agda observes that
the outermost term in `not` in a `λ`, which is typed using `⇒-I`. The
`⇒-I` rule in turn takes one argument, which Agda leaves as a hole.
⊢not = ⇒-I { }0
?0 : ∅ , x ⦂ 𝔹 ⊢ if ` x then false else true ⦂ 𝔹
Again we fill in the hole by typing C-c C-r. Agda observes that the
outermost term is now `if_then_else_`, which is typed using `𝔹-E`. The
`𝔹-E` rule in turn takes three arguments, which Agda leaves as holes.
⊢not = ⇒-I (𝔹-E { }0 { }1 { }2)
?0 : ∅ , x ⦂ 𝔹 ⊢ ` x ⦂
?1 : ∅ , x ⦂ 𝔹 ⊢ false ⦂ 𝔹
?2 : ∅ , x ⦂ 𝔹 ⊢ true ⦂ 𝔹
Again we fill in the three holes by typing C-c C-r in each. Agda observes
that `` ` x ``, `false`, and `true` are typed using `Ax`, `𝔹-I₂`, and
`𝔹-I₁` respectively. The `Ax` rule in turn takes an argument, to show
that `(∅ , x ⦂ 𝔹) x = just 𝔹`, which can in turn be specified with a
hole. After filling in all holes, the term is as above.
The entire process can be automated using Agsy, invoked with C-c C-a.
#### Non-examples
We can also show that terms are _not_ typeable. For example, here is
a formal proof that it is not possible to type the term `` true ·
false ``. In other words, no type `A` is the type of this term. It
cannot be typed, because doing so requires that the first term in the
application is both a boolean and a function.
\begin{code}
¬⊢₁ : ∀ {A} → ¬ (∅ ⊢ true · false ⦂ A)
¬⊢₁ (⇒-E () _)
\end{code}
As a second example, here is a formal proof that it is not possible to
type `` ƛ "x" ⇒ # "x" · # "x" `` It cannot be typed, because
doing so requires some types `A` and `B` such that `A ⇒ B ≡ A`.
\begin{code}
contradiction : ∀ {A B} → ¬ (A ⇒ B ≡ A)
contradiction ()
∋-injective : ∀ {Γ x A₁ A₂} → Γ ∋ x ⦂ A₁ → Γ ∋ x ⦂ A₂ → A₁ ≡ A₂
∋-injective Z Z = refl
∋-injective Z (S w≢ _) = ⊥-elim (w≢ refl)
∋-injective (S w≢ _) Z = ⊥-elim (w≢ refl)
∋-injective (S _ ∋w₁) (S _ ∋w₂) = ∋-injective ∋w₁ ∋w₂
¬⊢₂ : ∀ {A} → ¬ (∅ ⊢ ƛ "x" ⇒ # "x" · # "x" ⦂ A)
¬⊢₂ (⇒-I (⇒-E (Ax ∋x₁) (Ax ∋x₂))) = contradiction (∋-injective ∋x₁ ∋x₂)
\end{code}
#### Quiz
For each of the following, given a type `A` for which it is derivable,
or explain why there is no such `A`.
1. `` ∅ , y ⦂ A ⊢ λ[ x ⦂ 𝔹 ] ` x ⦂ 𝔹𝔹 ``
2. `` ∅ ⊢ λ[ y ⦂ 𝔹𝔹 ] λ[ x ⦂ 𝔹 ] ` y · ` x ⦂ A ``
3. `` ∅ ⊢ λ[ y ⦂ 𝔹𝔹 ] λ[ x ⦂ 𝔹 ] ` x · ` y ⦂ A ``
4. `` ∅ , x ⦂ A ⊢ λ[ y : 𝔹𝔹 ] `y · `x : A ``
For each of the following, give type `A`, `B`, `C`, and `D` for which it is derivable,
or explain why there are no such types.
1. `` ∅ ⊢ λ[ y ⦂ 𝔹𝔹𝔹 ] λ[ x ⦂ 𝔹 ] ` y · ` x ⦂ A ``
2. `` ∅ , x ⦂ A ⊢ x · x ⦂ B ``
3. `` ∅ , x ⦂ A , y ⦂ B ⊢ λ[ z ⦂ C ] ` x · (` y · ` z) ⦂ D ``
## Unicode
This chapter uses the following unicode
⇒ U+21D2: RIGHTWARDS DOUBLE ARROW (\=>)
ƛ U+019B: LATIN SMALL LETTER LAMBDA WITH STROKE (\Gl-)
⦂ U+2982: Z NOTATION TYPE COLON (\:)
· U+00B7: MIDDLE DOT (\cdot)
😇 U+1F607: SMILING FACE WITH HALO
😈 U+1F608: SMILING FACE WITH HORNS
U+2032: PRIME (\')
⟹ U+27F9: LONG RIGHTWARDS DOUBLE ARROW (\r6)
ξ U+03BE: GREEK SMALL LETTER XI (\Gx or \xi)
β U+03B2: GREEK SMALL LETTER BETA (\Gb or \beta)
Note that (U+2032: PRIME) is not the same as ' (U+0027: APOSTROPHE).

854
src/StlcPropNew.lagda Normal file
View file

@ -0,0 +1,854 @@
---
title : "StlcPropNew: Properties of STLC"
layout : page
permalink : /StlcPropNew
---
In this chapter, we develop the fundamental theory of the Simply
Typed Lambda Calculus---in particular, the type safety
theorem.
## Imports
\begin{code}
module StlcPropNew where
open import Function using (_∘_)
open import Data.Empty using (⊥; ⊥-elim)
open import Data.Maybe using (Maybe; just; nothing)
open import Data.Product using (_×_; ∃; ∃₂; _,_; ,_)
open import Data.Sum using (_⊎_; inj₁; inj₂)
open import Relation.Nullary using (¬_; Dec; yes; no)
open import Relation.Binary.PropositionalEquality using (_≡_; _≢_; refl; trans; sym)
open import Maps using (Id; _≟_; PartialMap)
open Maps.PartialMap using (∅; apply-∅; update-permute; just≢nothing; just-injective)
renaming (_,_↦_ to _,__)
open import Stlc
import Data.Nat using ()
\end{code}
## Canonical Forms
<!--
As we saw for the simple calculus in Chapter [Types]({{ "Types" | relative_url }}),
-->
The first step in establishing basic properties of reduction and typing
is to identify the possible _canonical forms_ (i.e., well-typed closed values)
belonging to each type. For function types the canonical forms are lambda-abstractions,
while for boolean types they are values `true` and `false`.
\begin{code}
data canonical_for_ : Term → Type → Set where
canonical-λ : ∀ {x A N B} → canonical (λ[ x A ] N) for (A ⇒ B)
canonical-true : canonical true for 𝔹
canonical-false : canonical false for 𝔹
canonical-forms : ∀ {L A} → ∅ ⊢ L A → Value L → canonical L for A
canonical-forms (Ax ()) ()
canonical-forms (⇒-I ⊢N) value-λ = canonical-λ
canonical-forms (⇒-E ⊢L ⊢M) ()
canonical-forms 𝔹-I₁ value-true = canonical-true
canonical-forms 𝔹-I₂ value-false = canonical-false
canonical-forms (𝔹-E ⊢L ⊢M ⊢N) ()
\end{code}
## Progress
As before, the _progress_ theorem tells us that closed, well-typed
terms are not stuck: either a well-typed term can take a reduction
step or it is a value.
\begin{code}
data Progress : Term → Set where
steps : ∀ {M N} → M ⟹ N → Progress M
done : ∀ {M} → Value M → Progress M
progress : ∀ {M A} → ∅ ⊢ M A → Progress M
\end{code}
<!--
The proof is a relatively straightforward extension of the progress proof we saw in
[Types]({{ "Types" | relative_url }}).
-->
We give the proof in English first, then the formal version.
_Proof_: By induction on the derivation of `∅ ⊢ M A`.
- The last rule of the derivation cannot be `Ax`,
since a variable is never well typed in an empty context.
- If the last rule of the derivation is `⇒-I`, `𝔹-I₁`, or `𝔹-I₂`
then, trivially, the term is a value.
- If the last rule of the derivation is `⇒-E`, then the term has the
form `L · M` for some `L` and `M`, where we know that `L` and `M`
are also well typed in the empty context, giving us two induction
hypotheses. By the first induction hypothesis, either `L`
can take a step or is a value.
- If `L` can take a step, then so can `L · M` by `ξ·₁`.
- If `L` is a value then consider `M`. By the second induction
hypothesis, either `M` can take a step or is a value.
- If `M` can take a step, then so can `L · M` by `ξ·₂`.
- If `M` is a value, then since `L` is a value with a
function type we know from the canonical forms lemma
that it must be a lambda abstraction,
and hence `L · M` can take a step by `βλ·`.
- If the last rule of the derivation is `𝔹-E`, then the term has
the form `if L then M else N` where `L` has type `𝔹`.
By the induction hypothesis, either `L` can take a step or is
a value.
- If `L` can take a step, then so can `if L then M else N` by `ξif`.
- If `L` is a value, then since it has type boolean we know from
the canonical forms lemma that it must be either `true` or
`false`.
- If `L` is `true`, then `if L then M else N` steps by `βif-true`
- If `L` is `false`, then `if L then M else N` steps by `βif-false`
This completes the proof.
\begin{code}
progress (Ax ())
progress (⇒-I ⊢N) = done value-λ
progress (⇒-E ⊢L ⊢M) with progress ⊢L
... | steps L⟹L = steps (ξ·₁ L⟹L)
... | done valueL with progress ⊢M
... | steps M⟹M = steps (ξ·₂ valueL M⟹M)
... | done valueM with canonical-forms ⊢L valueL
... | canonical-λ = steps (βλ· valueM)
progress 𝔹-I₁ = done value-true
progress 𝔹-I₂ = done value-false
progress (𝔹-E ⊢L ⊢M ⊢N) with progress ⊢L
... | steps L⟹L = steps (ξif L⟹L)
... | done valueL with canonical-forms ⊢L valueL
... | canonical-true = steps βif-true
... | canonical-false = steps βif-false
\end{code}
This code reads neatly in part because we consider the
`steps` option before the `done` option. We could, of course,
do it the other way around, but then the `...` abbreviation
no longer works, and we will need to write out all the arguments
in full. In general, the rule of thumb is to consider the easy case
(here `steps`) before the hard case (here `done`).
If you have two hard cases, you will have to expand out `...`
or introduce subsidiary functions.
#### Exercise: 3 stars, optional (progress_from_term_ind)
Show that progress can also be proved by induction on terms
instead of induction on typing derivations.
\begin{code}
postulate
progress : ∀ M {A} → ∅ ⊢ M A → Progress M
\end{code}
## Preservation
The other half of the type soundness property is the preservation
of types during reduction. For this, we need to develop
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), the story goes like this:
- The _preservation theorem_ is proved by induction on a typing derivation.
derivation, pretty much as we did in chapter [Types]({{ "Types" | relative_url }})
- The one case that is significantly different is the one for the
`βλ·` rule, whose 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 (closed) term
`V` for a variable `x` in a term `N` preserves the type of `N`.
The proof goes by induction on the form of `N` and requires
looking at all the different cases in the definition of
substitition. The lemma does not require that `V` be a value,
though in practice we only substitute values. The tricky cases
are the ones for variables and for function abstractions. In both
cases, we discover that we need to take a term `V` that has been
shown to be well-typed in some context `Γ` and consider the same
term in a different context `Γ′`. For this we prove a ...
- _context invariance_ lemma, showing that typing is preserved
under "inessential changes" to the context---a term `M`
well typed in `Γ` is also well typed in `Γ′`, so long as
all the free variables of `M` appear in both contexts.
And finally, for this, we need a careful definition of ...
- _free variables_ of a term---i.e., those variables
mentioned in a term and not bound in an enclosing
lambda abstraction.
To make Agda happy, we need to formalize the story in the opposite
order.
### Free Occurrences
A variable `x` appears _free_ in a term `M` if `M` contains an
occurrence of `x` that is not bound in an enclosing lambda abstraction.
For example:
- Variable `x` appears free, but `f` does not, in ``λ[ f (𝔹𝔹) ] ` f · ` x``.
- Both `f` and `x` appear free in ``(λ[ f (𝔹𝔹) ] ` f · ` x) · ` f``.
Indeed, `f` appears both bound and free in this term.
- No variables appear free in ``λ[ f (𝔹𝔹) ] λ[ x 𝔹 ] ` f · ` x``.
Formally:
\begin{code}
data _∈_ : Id → Term → Set where
free-` : ∀ {x} → x ∈ ` x
free-λ : ∀ {x y A N} → y ≢ x → x ∈ N → x ∈ (λ[ y A ] N)
free-·₁ : ∀ {x L M} → x ∈ L → x ∈ (L · M)
free-·₂ : ∀ {x L M} → x ∈ M → x ∈ (L · M)
free-if₁ : ∀ {x L M N} → x ∈ L → x ∈ (if L then M else N)
free-if₂ : ∀ {x L M N} → x ∈ M → x ∈ (if L then M else N)
free-if₃ : ∀ {x L M N} → x ∈ N → x ∈ (if L then M else N)
\end{code}
A term in which no variables appear free is said to be _closed_.
\begin{code}
_∉_ : Id → Term → Set
x ∉ M = ¬ (x ∈ M)
closed : Term → Set
closed M = ∀ {x} → x ∉ M
\end{code}
Here are proofs corresponding to the first two examples above.
\begin{code}
f≢x : f ≢ x
f≢x ()
example-free₁ : x ∈ (λ[ f (𝔹𝔹) ] ` f · ` x)
example-free₁ = free-λ f≢x (free-·₂ free-`)
example-free₂ : f ∉ (λ[ f (𝔹𝔹) ] ` f · ` x)
example-free₂ (free-λ f≢f _) = f≢f refl
\end{code}
#### Exercise: 1 star (free-in)
Prove formally the remaining examples given above.
\begin{code}
postulate
example-free₃ : x ∈ ((λ[ f (𝔹𝔹) ] ` f · ` x) · ` f)
example-free₄ : f ∈ ((λ[ f (𝔹𝔹) ] ` f · ` x) · ` f)
example-free₅ : x ∉ (λ[ f (𝔹𝔹) ] λ[ x 𝔹 ] ` f · ` x)
example-free₆ : f ∉ (λ[ f (𝔹𝔹) ] λ[ x 𝔹 ] ` f · ` x)
\end{code}
Although `_∈_` may appear to be a low-level technical definition,
understanding it is crucial to understanding the properties of
substitution, which are really the crux of the lambda calculus.
### Substitution
To prove that substitution preserves typing, we first need a technical
lemma connecting free variables and typing contexts. If variable `x`
appears free in term `M`, and if `M` is well typed in context `Γ`,
then `Γ` must assign a type to `x`.
\begin{code}
free-lemma : ∀ {x M A Γ} → x ∈ M → Γ ⊢ M A → ∃ λ B → Γ x ≡ just B
\end{code}
_Proof_: We show, by induction on the proof that `x` appears
free in `M`, that, for all contexts `Γ`, if `M` is well
typed under `Γ`, then `Γ` assigns some type to `x`.
- If the last rule used was `` free-` ``, then `M = `` `x ``, and from
the assumption that `M` is well typed under `Γ` we have
immediately that `Γ` assigns a type to `x`.
- If the last rule used was `free-·₁`, then `M = L · M` and `x`
appears free in `L`. Since `L` is well typed under `Γ`,
we can see from the typing rules that `L` must also be, and
the IH then tells us that `Γ` assigns `x` a type.
- Almost all the other cases are similar: `x` appears free in a
subterm of `M`, and since `M` is well typed under `Γ`, we
know the subterm of `M` in which `x` appears is well typed
under `Γ` as well, and the IH yields the desired conclusion.
- The only remaining case is `free-λ`. In this case `M =
λ[ y A ] N`, and `x` appears free in `N`; we also know that
`x` is different from `y`. The difference from the previous
cases is that whereas `M` is well typed under `Γ`, its
body `N` is well typed under `(Γ , y A)`, so the IH
allows us to conclude that `x` is assigned some type by the
extended context `(Γ , y A)`. To conclude that `Γ`
assigns a type to `x`, we appeal the decidable equality for names
`_≟_`, and note that `x` and `y` are different variables.
\begin{code}
free-lemma free-` (Ax Γx≡A) = (_ , Γx≡A)
free-lemma (free-·₁ x∈L) (⇒-E ⊢L ⊢M) = free-lemma x∈L ⊢L
free-lemma (free-·₂ x∈M) (⇒-E ⊢L ⊢M) = free-lemma x∈M ⊢M
free-lemma (free-if₁ x∈L) (𝔹-E ⊢L ⊢M ⊢N) = free-lemma x∈L ⊢L
free-lemma (free-if₂ x∈M) (𝔹-E ⊢L ⊢M ⊢N) = free-lemma x∈M ⊢M
free-lemma (free-if₃ x∈N) (𝔹-E ⊢L ⊢M ⊢N) = free-lemma x∈N ⊢N
free-lemma (free-λ {x} {y} y≢x x∈N) (⇒-I ⊢N) with free-lemma x∈N ⊢N
... | Γx≡C with y ≟ x
... | yes y≡x = ⊥-elim (y≢x y≡x)
... | no _ = Γx≡C
\end{code}
A subtle point: if the first argument of `free-λ` was of type
`x ≢ y` rather than of type `y ≢ x`, then the type of the
term `Γx≡C` would not simplify properly; instead, one would need
to rewrite with the symmetric equivalence.
As a second technical lemma, we need that any term `M` which is well
typed in the empty context is closed (has no free variables).
#### Exercise: 2 stars, optional (∅⊢-closed)
\begin{code}
postulate
∅⊢-closed : ∀ {M A} → ∅ ⊢ M A → closed M
\end{code}
<div class="hidden">
\begin{code}
∅⊢-closed : ∀ {M A} → ∅ ⊢ M A → closed M
∅⊢-closed {M} {A} ⊢M {x} x∈M with free-lemma x∈M ⊢M
... | (B , ∅x≡B) = just≢nothing (trans (sym ∅x≡B) (apply-∅ x))
\end{code}
</div>
Sometimes, when we have a proof `Γ ⊢ M A`, we will need to
replace `Γ` by a different context `Γ′`. When is it safe
to do this? Intuitively, the only variables we care about
in the context are those that appear free in `M`. So long
as the two contexts agree on those variables, one can be
exchanged for the other.
\begin{code}
context-lemma : ∀ {Γ Γ′ M A}
→ (∀ {x} → x ∈ M → Γ x ≡ Γ′ x)
→ Γ ⊢ M A
→ Γ′ ⊢ M A
\end{code}
_Proof_: By induction on the derivation of `Γ ⊢ M A`.
- If the last rule in the derivation was `Ax`, then `M = x`
and `Γ x ≡ just A`. By assumption, `Γ′ x = just A` as well, and
hence `Γ′ ⊢ M : A` by `Ax`.
- If the last rule was `⇒-I`, then `M = λ[ y : A] N`, with
`A = A ⇒ B` and `Γ , y A ⊢ N B`. The
induction hypothesis is that, for any context `Γ″`, if
`Γ , y : A` and `Γ″` assign the same types to all the
free variables in `N`, then `N` has type `B` under
`Γ″`. Let `Γ′` be a context which agrees with
`Γ` on the free variables in `N`; we must show
`Γ′ ⊢ λ[ y A] N A ⇒ B`.
By `⇒-I`, it suffices to show that `Γ′ , y:A ⊢ N B`.
By the IH (setting `Γ″ = Γ′ , y : A`), it suffices to show
that `Γ , y : A` and `Γ′ , y : A` agree on all the variables
that appear free in `N`.
Any variable occurring free in `N` must be either `y` or
some other variable. Clearly, `Γ , y : A` and `Γ′ , y : A`
agree on `y`. Otherwise, any variable other
than `y` that occurs free in `N` also occurs free in
`λ[ y : A] N`, and by assumption `Γ` and
`Γ′` agree on all such variables; hence so do
`Γ , y : A` and `Γ′ , y : A`.
- If the last rule was `⇒-E`, then `M = L · M`, with `Γ ⊢ L A ⇒ B`
and `Γ ⊢ M B`. One induction hypothesis states that for all
contexts `Γ′`, if `Γ′` agrees with `Γ` on the free variables in
`L` then `L` has type `A ⇒ B` under `Γ′`; there is a similar IH
for `M`. We must show that `L · M` also has type `B` under `Γ′`,
given the assumption that `Γ′` agrees with `Γ` on all the free
variables in `L · M`. By `⇒-E`, it suffices to show that `L` and
`M` each have the same type under `Γ′` as under `Γ`. But all free
variables in `L` are also free in `L · M`; in the proof, this is
expressed by composing `free-·₁ : ∀ {x} → x ∈ L → x ∈ L · M` with
`Γ~Γ′ : (∀ {x} → x ∈ L · M → Γ x ≡ Γ′ x)` to yield `Γ~Γ′ ∘ free-·₁
: ∀ {x} → x ∈ L → Γ x ≡ Γ′ x`. Similarly for `M`; hence the
desired result follows from the induction hypotheses.
- The remaining cases are similar to `⇒-E`.
\begin{code}
context-lemma Γ~Γ′ (Ax Γx≡A) rewrite (Γ~Γ′ free-`) = Ax Γx≡A
context-lemma {Γ} {Γ′} {λ[ x A ] N} Γ~Γ′ (⇒-I ⊢N) = ⇒-I (context-lemma Γx~Γx ⊢N)
where
Γx~Γx : ∀ {y} → y ∈ N → (Γ , x A) y ≡ (Γ′ , x A) y
Γx~Γx {y} y∈N with x ≟ y
... | yes refl = refl
... | no x≢y = Γ~Γ′ (free-λ x≢y y∈N)
context-lemma Γ~Γ′ (⇒-E ⊢L ⊢M) = ⇒-E (context-lemma (Γ~Γ′ ∘ free-·₁) ⊢L)
(context-lemma (Γ~Γ′ ∘ free-·₂) ⊢M)
context-lemma Γ~Γ′ 𝔹-I₁ = 𝔹-I₁
context-lemma Γ~Γ′ 𝔹-I₂ = 𝔹-I₂
context-lemma Γ~Γ′ (𝔹-E ⊢L ⊢M ⊢N) = 𝔹-E (context-lemma (Γ~Γ′ ∘ free-if₁) ⊢L)
(context-lemma (Γ~Γ′ ∘ free-if₂) ⊢M)
(context-lemma (Γ~Γ′ ∘ free-if₃) ⊢N)
\end{code}
Now we come to the conceptual heart of the proof that reduction
preserves types---namely, the observation that _substitution_
preserves types.
Formally, the _Substitution Lemma_ says this: Suppose we
have a term `N` with a free variable `x`, where `N` has
type `B` under the assumption that `x` has some type `A`.
Also, suppose that we have some other term `V`,
where `V` has type `A`. Then, since `V` satisfies
the assumption we made about `x` when typing `N`, we should be
able to substitute `V` for each of the occurrences of `x` in `N`
and obtain a new term that still has type `B`.
_Lemma_: If `Γ , x A ⊢ N B` and `∅ ⊢ V A`, then
`Γ ⊢ (N [ x := V ]) B`.
\begin{code}
preservation-[:=] : ∀ {Γ x A N B V}
→ (Γ , x A) ⊢ N B
→ ∅ ⊢ V A
→ Γ ⊢ (N [ x := V ]) B
\end{code}
One technical subtlety in the statement of the lemma is that we assume
`V` is closed; it has type `A` in the _empty_ context. This
assumption simplifies the `λ` case of the proof because the context
invariance lemma then tells us that `V` has type `A` in any context at
all---we don't have to worry about free variables in `V` clashing with
the variable being introduced into the context by `λ`. It is possible
to prove the theorem under the more general assumption `Γ ⊢ V A`,
but the proof is more difficult.
<!--
Intuitively, the substitution lemma says that substitution and typing can
be done in either order: we can either assign types to the terms
`N` and `V` separately (under suitable contexts) and then combine
them using substitution, or we can substitute first and then
assign a type to `N [ x := V ]`---the result is the same either
way.
-->
_Proof_: By induction on the derivation of `Γ , x A ⊢ N B`,
we show that if `∅ ⊢ V A` then `Γ ⊢ N [ x := V ] B`.
- If `N` is a variable there are two cases to consider,
depending on whether `N` is `x` or some other variable.
- If `N = `` `x ``, then from `Γ , x A ⊢ x B`
we know that looking up `x` in `Γ , x : A` gives
`just B`, but we already know it gives `just A`;
applying injectivity for `just` we conclude that `A ≡ B`.
We must show that `x [ x := V] = V`
has type `A` under `Γ`, given the assumption that
`V` has type `A` under the empty context. This
follows from context invariance: if a closed term has type
`A` in the empty context, it has that type in any context.
- If `N` is some variable `x` different from `x`, then
we need only note that `x` has the same type under `Γ , x A`
as under `Γ`.
- If `N` is an abstraction `λ[ x A ] N`, then the IH tells us,
for all `Γ′`́ and `B`, that if `Γ′ , x A ⊢ N B`
and `∅ ⊢ V A`, then `Γ′ ⊢ N [ x := V ] B`.
The substitution in the conclusion behaves differently
depending on whether `x` and `x` are the same variable.
First, suppose `x ≡ x`. Then, by the definition of
substitution, `N [ x := V] = N`, so we just need to show `Γ ⊢ N B`.
But we know `Γ , x A ⊢ N B` and, since `x ≡ x`
does not appear free in `λ[ x A ] N`, the context invariance
lemma yields `Γ ⊢ N B`.
Second, suppose `x ≢ x`. We know `Γ , x A , x A ⊢ N B`
by inversion of the typing relation, from which
`Γ , x A , x A ⊢ N B` follows by update permute,
so the IH applies, giving us `Γ , x A ⊢ N [ x := V ] B`
By `⇒-I`, we have `Γ ⊢ λ[ x A ] (N [ x := V ]) A ⇒ B`
and the definition of substitution (noting `x ≢ x`) gives
`Γ ⊢ (λ[ x A ] N) [ x := V ] A ⇒ B` as required.
- If `N` is an application `L · M`, the result follows
straightforwardly from the definition of substitution and the
induction hypotheses.
- The remaining cases are similar to application.
We need a lemmas stating that a closed term can be weakened to any context.
\begin{code}
weaken-closed : ∀ {V A Γ} → ∅ ⊢ V A → Γ ⊢ V A
weaken-closed {V} {A} {Γ} ⊢V = context-lemma Γ~Γ′ ⊢V
where
Γ~Γ′ : ∀ {x} → x ∈ V → ∅ x ≡ Γ x
Γ~Γ′ {x} x∈V = ⊥-elim (x∉V x∈V)
where
x∉V : ¬ (x ∈ V)
x∉V = ∅⊢-closed ⊢V {x}
\end{code}
\begin{code}
preservation-[:=] {Γ} {x} {A} (Ax {Γ,xA} {x} {B} [Γ,xA]x≡B) ⊢V with x ≟ x
...| yes x≡x rewrite just-injective [Γ,xA]x≡B = weaken-closed ⊢V
...| no x≢x = Ax [Γ,xA]x≡B
preservation-[:=] {Γ} {x} {A} {λ[ x A ] N} {.A ⇒ B} {V} (⇒-I ⊢N) ⊢V with x ≟ x
...| yes x≡x rewrite x≡x = context-lemma Γ′~Γ (⇒-I ⊢N)
where
Γ′~Γ : ∀ {y} → y ∈ (λ[ x A ] N) → (Γ , x A) y ≡ Γ y
Γ′~Γ {y} (free-λ x≢y y∈N) with x ≟ y
...| yes x≡y = ⊥-elim (x≢y x≡y)
...| no _ = refl
...| no x≢x = ⇒-I ⊢NV
where
xx⊢N : Γ , x A , x A ⊢ N B
xx⊢N rewrite update-permute Γ x A x A x≢x = ⊢N
⊢NV : (Γ , x A) ⊢ N [ x := V ] B
⊢NV = preservation-[:=] xx⊢N ⊢V
preservation-[:=] (⇒-E ⊢L ⊢M) ⊢V = ⇒-E (preservation-[:=] ⊢L ⊢V) (preservation-[:=] ⊢M ⊢V)
preservation-[:=] 𝔹-I₁ ⊢V = 𝔹-I₁
preservation-[:=] 𝔹-I₂ ⊢V = 𝔹-I₂
preservation-[:=] (𝔹-E ⊢L ⊢M ⊢N) ⊢V =
𝔹-E (preservation-[:=] ⊢L ⊢V) (preservation-[:=] ⊢M ⊢V) (preservation-[:=] ⊢N ⊢V)
\end{code}
### Main Theorem
We now have the tools we need to prove preservation: if a closed
term `M` has type `A` and takes a step to `N`, then `N`
is also a closed term with type `A`. In other words, small-step
reduction preserves types.
\begin{code}
preservation : ∀ {M N A} → ∅ ⊢ M A → M ⟹ N → ∅ ⊢ N A
\end{code}
_Proof_: By induction on the derivation of `∅ ⊢ M A`.
- We can immediately rule out `Ax`, `⇒-I`, `𝔹-I₁`, and
`𝔹-I₂` as the final rules in the derivation, since in each of
these cases `M` cannot take a step.
- If the last rule in the derivation was `⇒-E`, then `M = L · M`.
There are three cases to consider, one for each rule that
could have been used to show that `L · M` takes a step to `N`.
- If `L · M` takes a step by `ξ·₁`, with `L` stepping to
`L`, then by the IH `L` has the same type as `L`, and
hence `L · M` has the same type as `L · M`.
- The `ξ·₂` case is similar.
- If `L · M` takes a step by `β⇒`, then `L = λ[ x A ] N` and `M
= V` and `L · M` steps to `N [ x := V]`; the desired result now
follows from the fact that substitution preserves types.
- If the last rule in the derivation was `if`, then `M = if L
then M else N`, and there are again three cases depending on
how `if L then M else N` steps.
- If it steps via `β𝔹₁` or `βB₂`, the result is immediate, since
`M` and `N` have the same type as `if L then M else N`.
- Otherwise, `L` steps by `ξif`, and the desired conclusion
follows directly from the induction hypothesis.
\begin{code}
preservation (Ax Γx≡A) ()
preservation (⇒-I ⊢N) ()
preservation (⇒-E (⇒-I ⊢N) ⊢V) (βλ· valueV) = preservation-[:=] ⊢N ⊢V
preservation (⇒-E ⊢L ⊢M) (ξ·₁ L⟹L) with preservation ⊢L L⟹L
...| ⊢L = ⇒-E ⊢L ⊢M
preservation (⇒-E ⊢L ⊢M) (ξ·₂ valueL M⟹M) with preservation ⊢M M⟹M
...| ⊢M = ⇒-E ⊢L ⊢M
preservation 𝔹-I₁ ()
preservation 𝔹-I₂ ()
preservation (𝔹-E 𝔹-I₁ ⊢M ⊢N) βif-true = ⊢M
preservation (𝔹-E 𝔹-I₂ ⊢M ⊢N) βif-false = ⊢N
preservation (𝔹-E ⊢L ⊢M ⊢N) (ξif L⟹L) with preservation ⊢L L⟹L
...| ⊢L = 𝔹-E ⊢L ⊢M ⊢N
\end{code}
#### Exercise: 2 stars, recommended (subject_expansion_stlc)
<!--
An exercise in the [Types]({{ "Types" | relative_url }}) chapter asked about the
subject expansion property for the simple language of arithmetic and boolean
expressions. Does this property hold for STLC? That is, is it always the case
that, if `M ==> N` and `∅ ⊢ N A`, then `∅ ⊢ M A`? It is easy to find a
counter-example with conditionals, find one not involving conditionals.
-->
We say that `M` _reduces_ to `N` if `M ⟹ N`,
and that `M` _expands_ to `N` if `N ⟹ M`.
The preservation property is sometimes called _subject reduction_.
Its opposite is _subject expansion_, which holds if
`M ==> N` and `∅ ⊢ N A`, then `∅ ⊢ M A`.
Find two counter-examples to subject expansion, one
with conditionals and one not involving conditionals.
## Type Soundness
#### Exercise: 2 stars, optional (type_soundness)
Put progress and preservation together and show that a well-typed
term can _never_ reach a stuck state.
\begin{code}
Normal : Term → Set
Normal M = ∀ {N} → ¬ (M ⟹ N)
Stuck : Term → Set
Stuck M = Normal M × ¬ Value M
postulate
Soundness : ∀ {M N A} → ∅ ⊢ M A → M ⟹* N → ¬ (Stuck N)
\end{code}
<div class="hidden">
\begin{code}
Soundness : ∀ {M N A} → ∅ ⊢ M A → M ⟹* N → ¬ (Stuck N)
Soundness ⊢M (M ∎) (¬M⟹N , ¬ValueM) with progress ⊢M
... | steps M⟹N = ¬M⟹N M⟹N
... | done ValueM = ¬ValueM ValueM
Soundness {L} {N} {A} ⊢L (_⟹⟨_⟩_ .L {M} {.N} L⟹M M⟹*N) = Soundness ⊢M M⟹*N
where
⊢M : ∅ ⊢ M A
⊢M = preservation ⊢L L⟹M
\end{code}
</div>
## Uniqueness of Types
#### Exercise: 3 stars (types_unique)
Another nice property of the STLC is that types are unique: a
given term (in a given context) has at most one type.
Formalize this statement and prove it.
## Additional Exercises
#### Exercise: 1 star (progress_preservation_statement)
Without peeking at their statements above, write down the progress
and preservation theorems for the simply typed lambda-calculus.
#### Exercise: 2 stars (stlc_variation1)
Suppose we add a new term `zap` with the following reduction rule
--------- (ST_Zap)
M ⟹ zap
and the following typing rule:
----------- (T_Zap)
Γ ⊢ zap : A
Which of the following properties of the STLC remain true in
the presence of these rules? For each property, write either
"remains true" or "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars (stlc_variation2)
Suppose instead that we add a new term `foo` with the following
reduction rules:
---------------------- (ST_Foo1)
(λ[ x A ] ` x) ⟹ foo
----------- (ST_Foo2)
foo ⟹ true
Which of the following properties of the STLC remain true in
the presence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars (stlc_variation3)
Suppose instead that we remove the rule `ξ·₁` from the `⟹`
relation. Which of the following properties of the STLC remain
true in the absence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars, optional (stlc_variation4)
Suppose instead that we add the following new rule to the
reduction relation:
---------------------------------- (ST_FunnyIfTrue)
(if true then t_1 else t_2) ⟹ true
Which of the following properties of the STLC remain true in
the presence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars, optional (stlc_variation5)
Suppose instead that we add the following new rule to the typing
relation:
Γ ⊢ L 𝔹𝔹𝔹
Γ ⊢ M 𝔹
------------------------------ (T_FunnyApp)
Γ ⊢ L · M 𝔹
Which of the following properties of the STLC remain true in
the presence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars, optional (stlc_variation6)
Suppose instead that we add the following new rule to the typing
relation:
Γ ⊢ L 𝔹
Γ ⊢ M 𝔹
--------------------- (T_FunnyApp')
Γ ⊢ L · M 𝔹
Which of the following properties of the STLC remain true in
the presence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
#### Exercise: 2 stars, optional (stlc_variation7)
Suppose we add the following new rule to the typing relation
of the STLC:
-------------------- (T_FunnyAbs)
∅ ⊢ λ[ x 𝔹 ] N 𝔹
Which of the following properties of the STLC remain true in
the presence of this rule? For each one, write either
"remains true" or else "becomes false." If a property becomes
false, give a counterexample.
- Determinism of `step`
- Progress
- Preservation
### Exercise: STLC with Arithmetic
To see how the STLC might function as the core of a real
programming language, let's extend it with a concrete base
type of numbers and some constants and primitive
operators.
To types, we add a base type of numbers (and remove
booleans, for brevity).
\begin{code}
data Type : Set where
_⇒_ : Type → Type → Type
: Type
\end{code}
To terms, we add natural number constants, along with
plus, minus, and testing for zero.
\begin{code}
data Term : Set where
`_ : Id → Term
λ[__]_ : Id → Type → Term → Term
_·_ : Term → Term → Term
#_ : Data.Nat. → Term
_+_ : Term → Term → Term
_-_ : Term → Term → Term
if0_then_else_ : Term → Term → Term → Term
\end{code}
(Assume that `m - n` returns `0` if `m` is less than `n`.)
#### Exercise: 4 stars (stlc_arith)
Finish formalizing the definition and properties of the STLC extended
with arithmetic. Specifically:
- Copy the whole development of STLC that we went through above (from
the definition of values through the Type Soundness theorem), and
paste it into the file at this point.
- Extend the definitions of the `subst` operation and the `step`
relation to include appropriate clauses for the arithmetic operators.
- Extend the proofs of all the properties (up to `soundness`) of
the original STLC to deal with the new syntactic forms. Make
sure Agda accepts the whole file.

View file

@ -724,7 +724,11 @@ free-lemma (⊢Y ⊢M) w∈ = free-lemma ⊢M w∈
### Substitution preserves types
\begin{code}
-- trivial : Set
-- trivial = ∀ ρ x → ρ x ≡ ` x ⊎ closed (ρ x)
⊢subst : ∀ {Γ Δ ρ}
-- → (∀ {x A} → Γ ∋ x `: A → trivial ρ x)
→ (∀ {x A} → Γ ∋ x `: A → Δ ⊢ ρ x `: A)
-------------------------------------------------
→ (∀ {M A} → Γ ⊢ M `: A → Δ ⊢ subst ρ M `: A)
@ -762,13 +766,18 @@ Let's look at examples. Assume `M` is closed. Example 1.
Example 2.
subst (∅ , "y" ↦ ` "y" , "x" ↦ M) (`λ "y" `→ ` "x" · ` "y")
subst (∅ , "y" ↦ N , "x" ↦ M) (`λ "y" `→ ` "x" · ` "y")
`λ "y" `→ subst (∅ , "y" ↦ ` "y" , "x" ↦ M , "y" ↦ ` "y") (` "x" · ` "y")
`λ "y" `→ subst (∅ , "y" ↦ ` N , "x" ↦ M , "y" ↦ ` "y") (` "x" · ` "y")
`λ "y" `→ (M · ` "y")
The hypotheses of the theorem appear to be violated. Drat!
Before I wrote: "The hypotheses of the theorem appear to be violated. Drat!"
But let's assume that ``M `: A``, ``N `: B``, and the lambda bound `y` has type `C`.
Then ``Γ ∋ y `: B`` will not hold for the extended `ρ` because of interference
by the earlier `y`. So I'm not sure the hypothesis is violated.
\begin{code}
⊢substitution : ∀ {Γ x A N B M}

View file

@ -11,7 +11,7 @@ a fresh name whenever substituting for a lambda term.
## Imports
\begin{code}
module TypedFresh where
module fresh.TypedFresh where
\end{code}
\begin{code}