ReferencesTyping Mutable References
- mutable pointer structures
- non-local control constructs (exceptions, continuations, etc.)
- process synchronization and communication
- etc.
Definitions
- keep the mechanisms for name binding (abstraction, let) the same;
- introduce new, explicit operations for allocating, changing, and looking up the contents of references (pointers).
Syntax
Module STLCRef.
The basic operations on references are allocation,
dereferencing, and assignment.
- To allocate a reference, we use the ref operator, providing
an initial value for the new cell. For example, ref 5
creates a new cell containing the value 5, and reduces to
a reference to that cell.
- To read the current value of this cell, we use the
dereferencing operator !; for example, !(ref 5) reduces
to 5.
- To change the value stored in a cell, we use the assignment operator. If r is a reference, r := 7 will store the value 7 in the cell referenced by r.
Types
T ::= Nat
| Unit
| T → T
| Ref T
| Unit
| T → T
| Ref T
Inductive ty : Type :=
| Nat : ty
| Unit : ty
| Arrow : ty → ty → ty
| Ref : ty → ty.
| Nat : ty
| Unit : ty
| Arrow : ty → ty → ty
| Ref : ty → ty.
Terms
t ::= ... Terms
| ref t allocation
| !t dereference
| t := t assignment
| l location
Inductive tm : Type :=
(* STLC with numbers: *)
| var : string → tm
| app : tm → tm → tm
| abs : string → ty → tm → tm
| const : nat → tm
| scc : tm → tm
| prd : tm → tm
| mlt : tm → tm → tm
| test0 : tm → tm → tm → tm
(* New terms: *)
| unit : tm
| ref : tm → tm
| deref : tm → tm
| assign : tm → tm → tm
| loc : nat → tm.
(* STLC with numbers: *)
| var : string → tm
| app : tm → tm → tm
| abs : string → ty → tm → tm
| const : nat → tm
| scc : tm → tm
| prd : tm → tm
| mlt : tm → tm → tm
| test0 : tm → tm → tm → tm
(* New terms: *)
| unit : tm
| ref : tm → tm
| deref : tm → tm
| assign : tm → tm → tm
| loc : nat → tm.
Typing (Preview)
Gamma ⊢ t1 : T1 | (T_Ref) |
Gamma ⊢ ref t1 : Ref T1 |
Gamma ⊢ t1 : Ref T11 | (T_Deref) |
Gamma ⊢ !t1 : T11 |
Gamma ⊢ t1 : Ref T11 | |
Gamma ⊢ t2 : T11 | (T_Assign) |
Gamma ⊢ t1 := t2 : Unit |
Values and Substitution
Inductive value : tm → Prop :=
| v_abs : ∀x T t,
value (abs x T t)
| v_nat : ∀n,
value (const n)
| v_unit :
value unit
| v_loc : ∀l,
value (loc l).
Hint Constructors value.
| v_abs : ∀x T t,
value (abs x T t)
| v_nat : ∀n,
value (const n)
| v_unit :
value unit
| v_loc : ∀l,
value (loc l).
Hint Constructors value.
Extending substitution to handle the new syntax of terms is
straightforward.
Fixpoint subst (x:string) (s:tm) (t:tm) : tm :=
match t with
| var x' ⇒
if eqb_string x x' then s else t
| app t1 t2 ⇒
app (subst x s t1) (subst x s t2)
| abs x' T t1 ⇒
if eqb_string x x' then t else abs x' T (subst x s t1)
| const n ⇒
t
| scc t1 ⇒
scc (subst x s t1)
| prd t1 ⇒
prd (subst x s t1)
| mlt t1 t2 ⇒
mlt (subst x s t1) (subst x s t2)
| test0 t1 t2 t3 ⇒
test0 (subst x s t1) (subst x s t2) (subst x s t3)
| unit ⇒
t
| ref t1 ⇒
ref (subst x s t1)
| deref t1 ⇒
deref (subst x s t1)
| assign t1 t2 ⇒
assign (subst x s t1) (subst x s t2)
| loc _ ⇒
t
end.
Notation "'[' x ':=' s ']' t" := (subst x s t) (at level 20).
match t with
| var x' ⇒
if eqb_string x x' then s else t
| app t1 t2 ⇒
app (subst x s t1) (subst x s t2)
| abs x' T t1 ⇒
if eqb_string x x' then t else abs x' T (subst x s t1)
| const n ⇒
t
| scc t1 ⇒
scc (subst x s t1)
| prd t1 ⇒
prd (subst x s t1)
| mlt t1 t2 ⇒
mlt (subst x s t1) (subst x s t2)
| test0 t1 t2 t3 ⇒
test0 (subst x s t1) (subst x s t2) (subst x s t3)
| unit ⇒
t
| ref t1 ⇒
ref (subst x s t1)
| deref t1 ⇒
deref (subst x s t1)
| assign t1 t2 ⇒
assign (subst x s t1) (subst x s t2)
| loc _ ⇒
t
end.
Notation "'[' x ':=' s ']' t" := (subst x s t) (at level 20).
Side Effects and Sequencing
r:=succ(!r); !ras an abbreviation for
(\x:Unit. !r) (r := succ(!r)).
Definition tseq t1 t2 :=
app (abs "x" Unit t2) t1.
app (abs "x" Unit t2) t1.
References and Aliasing
let r = ref 5 in let s = r in s := 82; (!r)+1the cell referenced by r will contain the value 82, while the result of the whole expression will be 83. The references r and s are said to be aliases for the same cell.
r := 5; r := !sassigns 5 to r and then immediately overwrites it with s's current value; this has exactly the same effect as the single assignment
r := !sunless we happen to do it in a context where r and s are aliases for the same cell!
Shared State
let c = ref 0 in let incc = \_:Unit. (c := succ (!c); !c) in let decc = \_:Unit. (c := pred (!c); !c) in ...
Objects
newcounter = \_:Unit. let c = ref 0 in let incc = \_:Unit. (c := succ (!c); !c) in let decc = \_:Unit. (c := pred (!c); !c) in {i=incc, d=decc}
let c1 = newcounter unit in let c2 = newcounter unit in // Note that we've allocated two separate storage cells now! let r1 = c1.i unit in let r2 = c2.i unit in r2 // yields 1, not 2!
References to Compound Types
equal = fix (\eq:Nat->Nat->Bool. \m:Nat. \n:Nat. if m=0 then iszero n else if n=0 then false else eq (pred m) (pred n))To build a new array, we allocate a reference cell and fill it with a function that, when given an index, always returns 0.
newarray = \_:Unit. ref (\n:Nat.0)To look up an element of an array, we simply apply the function to the desired index.
lookup = \a:NatArray. \n:Nat. (!a) nThe interesting part of the encoding is the update function. It takes an array, an index, and a new value to be stored at that index, and does its job by creating (and storing in the reference) a new function that, when it is asked for the value at this very index, returns the new value that was given to update, while on all other indices it passes the lookup to the function that was previously stored in the reference.
update = \a:NatArray. \m:Nat. \v:Nat. let oldf = !a in a := (\n:Nat. if equal m n then v else oldf n);References to values containing other references can also be very useful, allowing us to define data structures such as mutable lists and trees.
Null References
- in C, a pointer variable can contain either a valid pointer
into the heap or the special value NULL
- source of many errors and much tricky reasoning
- (any pointer may potentially be "not there")
- but occasionally useful
- easy to implement here using references plus options (which
can be built out of disjoint sum types)
Option T = Unit + T NullableRef T = Option (Ref T)
Garbage Collection
Exercise: 2 stars, standard (type_safety_violation)
Show how this can lead to a violation of type safety.
(* FILL IN HERE *)
☐
Locations
- Concretely: An array of 8-bit bytes, indexed by 32-bit integers.
- More abstractly: a list (or array) of values
- Even more abstractly: a partial function from locations to values.
Stores
Definition store := list tm.
We use store_lookup n st to retrieve the value of the reference
cell at location n in the store st. Note that we must give a
default value to nth in case we try looking up an index which is
too large. (In fact, we will never actually do this, but proving
that we don't will require a bit of work.)
Definition store_lookup (n:nat) (st:store) :=
nth n st unit.
nth n st unit.
To update the store, we use the replace function, which replaces
the contents of a cell at a particular index.
Fixpoint replace {A:Type} (n:nat) (x:A) (l:list A) : list A :=
match l with
| nil ⇒ nil
| h :: t ⇒
match n with
| O ⇒ x :: t
| S n' ⇒ h :: replace n' x t
end
end.
Lemma replace_nil : ∀A n (x:A),
replace n x nil = nil.
Lemma length_replace : ∀A n x (l:list A),
length (replace n x l) = length l.
Lemma lookup_replace_eq : ∀l t st,
l < length st →
store_lookup l (replace l t st) = t.
Lemma lookup_replace_neq : ∀l1 l2 t st,
l1 ≠ l2 →
store_lookup l1 (replace l2 t st) = store_lookup l1 st.
match l with
| nil ⇒ nil
| h :: t ⇒
match n with
| O ⇒ x :: t
| S n' ⇒ h :: replace n' x t
end
end.
Lemma replace_nil : ∀A n (x:A),
replace n x nil = nil.
Lemma length_replace : ∀A n x (l:list A),
length (replace n x l) = length l.
Lemma lookup_replace_eq : ∀l t st,
l < length st →
store_lookup l (replace l t st) = t.
Lemma lookup_replace_neq : ∀l1 l2 t st,
l1 ≠ l2 →
store_lookup l1 (replace l2 t st) = store_lookup l1 st.
Reduction
value v2 | (ST_AppAbs) |
(\a:T.t12) v2 / st --> [a:=v2]t12 / st |
t1 / st --> t1' / st' | (ST_App1) |
t1 t2 / st --> t1' t2 / st' |
value v1 t2 / st --> t2' / st' | (ST_App2) |
v1 t2 / st --> v1 t2' / st' |
(ST_RefValue) | |
ref v1 / st --> loc |st| / st,v1 |
t1 / st --> t1' / st' | (ST_Ref) |
ref t1 / st --> ref t1' / st' |
l < |st| | (ST_DerefLoc) |
!(loc l) / st --> lookup l st / st |
t1 / st --> t1' / st' | (ST_Deref) |
!t1 / st --> !t1' / st' |
l < |st| | (ST_Assign) |
loc l := v2 / st --> unit / replace l v2 st |
t1 / st --> t1' / st' | (ST_Assign1) |
t1 := t2 / st --> t1' := t2 / st' |
t2 / st --> t2' / st' | (ST_Assign2) |
v1 := t2 / st --> v1 := t2' / st' |
Reserved Notation "t1 '/' st1 '-->' t2 '/' st2"
(at level 40, st1 at level 39, t2 at level 39).
Import ListNotations.
Inductive step : tm * store → tm * store → Prop :=
| ST_AppAbs : ∀x T t12 v2 st,
value v2 →
app (abs x T t12) v2 / st --> [x:=v2]t12 / st
| ST_App1 : ∀t1 t1' t2 st st',
t1 / st --> t1' / st' →
app t1 t2 / st --> app t1' t2 / st'
| ST_App2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
app v1 t2 / st --> app v1 t2'/ st'
| ST_SuccNat : ∀n st,
scc (const n) / st --> const (S n) / st
| ST_Succ : ∀t1 t1' st st',
t1 / st --> t1' / st' →
scc t1 / st --> scc t1' / st'
| ST_PredNat : ∀n st,
prd (const n) / st --> const (pred n) / st
| ST_Pred : ∀t1 t1' st st',
t1 / st --> t1' / st' →
prd t1 / st --> prd t1' / st'
| ST_MultNats : ∀n1 n2 st,
mlt (const n1) (const n2) / st --> const (mult n1 n2) / st
| ST_Mult1 : ∀t1 t2 t1' st st',
t1 / st --> t1' / st' →
mlt t1 t2 / st --> mlt t1' t2 / st'
| ST_Mult2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
mlt v1 t2 / st --> mlt v1 t2' / st'
| ST_If0 : ∀t1 t1' t2 t3 st st',
t1 / st --> t1' / st' →
test0 t1 t2 t3 / st --> test0 t1' t2 t3 / st'
| ST_If0_Zero : ∀t2 t3 st,
test0 (const 0) t2 t3 / st --> t2 / st
| ST_If0_Nonzero : ∀n t2 t3 st,
test0 (const (S n)) t2 t3 / st --> t3 / st
| ST_RefValue : ∀v1 st,
value v1 →
ref v1 / st --> loc (length st) / (st ++ v1::nil)
| ST_Ref : ∀t1 t1' st st',
t1 / st --> t1' / st' →
ref t1 / st --> ref t1' / st'
| ST_DerefLoc : ∀st l,
l < length st →
deref (loc l) / st --> store_lookup l st / st
| ST_Deref : ∀t1 t1' st st',
t1 / st --> t1' / st' →
deref t1 / st --> deref t1' / st'
| ST_Assign : ∀v2 l st,
value v2 →
l < length st →
assign (loc l) v2 / st --> unit / replace l v2 st
| ST_Assign1 : ∀t1 t1' t2 st st',
t1 / st --> t1' / st' →
assign t1 t2 / st --> assign t1' t2 / st'
| ST_Assign2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
assign v1 t2 / st --> assign v1 t2' / st'
where "t1 '/' st1 '-->' t2 '/' st2" := (step (t1,st1) (t2,st2)).
(at level 40, st1 at level 39, t2 at level 39).
Import ListNotations.
Inductive step : tm * store → tm * store → Prop :=
| ST_AppAbs : ∀x T t12 v2 st,
value v2 →
app (abs x T t12) v2 / st --> [x:=v2]t12 / st
| ST_App1 : ∀t1 t1' t2 st st',
t1 / st --> t1' / st' →
app t1 t2 / st --> app t1' t2 / st'
| ST_App2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
app v1 t2 / st --> app v1 t2'/ st'
| ST_SuccNat : ∀n st,
scc (const n) / st --> const (S n) / st
| ST_Succ : ∀t1 t1' st st',
t1 / st --> t1' / st' →
scc t1 / st --> scc t1' / st'
| ST_PredNat : ∀n st,
prd (const n) / st --> const (pred n) / st
| ST_Pred : ∀t1 t1' st st',
t1 / st --> t1' / st' →
prd t1 / st --> prd t1' / st'
| ST_MultNats : ∀n1 n2 st,
mlt (const n1) (const n2) / st --> const (mult n1 n2) / st
| ST_Mult1 : ∀t1 t2 t1' st st',
t1 / st --> t1' / st' →
mlt t1 t2 / st --> mlt t1' t2 / st'
| ST_Mult2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
mlt v1 t2 / st --> mlt v1 t2' / st'
| ST_If0 : ∀t1 t1' t2 t3 st st',
t1 / st --> t1' / st' →
test0 t1 t2 t3 / st --> test0 t1' t2 t3 / st'
| ST_If0_Zero : ∀t2 t3 st,
test0 (const 0) t2 t3 / st --> t2 / st
| ST_If0_Nonzero : ∀n t2 t3 st,
test0 (const (S n)) t2 t3 / st --> t3 / st
| ST_RefValue : ∀v1 st,
value v1 →
ref v1 / st --> loc (length st) / (st ++ v1::nil)
| ST_Ref : ∀t1 t1' st st',
t1 / st --> t1' / st' →
ref t1 / st --> ref t1' / st'
| ST_DerefLoc : ∀st l,
l < length st →
deref (loc l) / st --> store_lookup l st / st
| ST_Deref : ∀t1 t1' st st',
t1 / st --> t1' / st' →
deref t1 / st --> deref t1' / st'
| ST_Assign : ∀v2 l st,
value v2 →
l < length st →
assign (loc l) v2 / st --> unit / replace l v2 st
| ST_Assign1 : ∀t1 t1' t2 st st',
t1 / st --> t1' / st' →
assign t1 t2 / st --> assign t1' t2 / st'
| ST_Assign2 : ∀v1 t2 t2' st st',
value v1 →
t2 / st --> t2' / st' →
assign v1 t2 / st --> assign v1 t2' / st'
where "t1 '/' st1 '-->' t2 '/' st2" := (step (t1,st1) (t2,st2)).
One slightly ugly point should be noted here: In the ST_RefValue
rule, we extend the state by writing st ++ v1::nil rather than
the more natural st ++ [v1]. The reason for this is that the
notation we've defined for substitution uses square brackets,
which clash with the standard library's notation for lists.
Hint Constructors step.
Definition multistep := (multi step).
Notation "t1 '/' st '-->*' t2 '/' st'" :=
(multistep (t1,st) (t2,st'))
(at level 40, st at level 39, t2 at level 39).
Definition multistep := (multi step).
Notation "t1 '/' st '-->*' t2 '/' st'" :=
(multistep (t1,st) (t2,st'))
(at level 40, st at level 39, t2 at level 39).
Typing
Definition context := partial_map ty.
Store typings
Gamma ⊢ lookup l st : T1 | |
Gamma ⊢ loc l : Ref T1 |
Gamma; st ⊢ lookup l st : T1 | |
Gamma; st ⊢ loc l : Ref T1 |
[\x:Nat. (!(loc 1)) x, \x:Nat. (!(loc 0)) x]
Exercise: 2 stars, standard (cyclic_store)
Can you find a term whose reduction will create this particular cyclic store?
Definition store_ty := list ty.
The store_Tlookup function retrieves the type at a particular
index.
Definition store_Tlookup (n:nat) (ST:store_ty) :=
nth n ST Unit.
nth n ST Unit.
Suppose we are given a store typing ST describing the store
st in which some term t will be reduced. Then we can use
ST to calculate the type of the result of t without ever
looking directly at st. For example, if ST is [Unit,
Unit→Unit], then we can immediately infer that !(loc 1) has
type Unit→Unit. More generally, the typing rule for locations
can be reformulated in terms of store typings like this:
That is, as long as l is a valid location, we can compute the
type of l just by looking it up in ST. Typing is again a
four-place relation, but it is parameterized on a store typing
rather than a concrete store. The rest of the typing rules are
analogously augmented with store typings.
l < |ST| | |
Gamma; ST ⊢ loc l : Ref (lookup l ST) |
The Typing Relation
l < |ST| | (T_Loc) |
Gamma; ST ⊢ loc l : Ref (lookup l ST) |
Gamma; ST ⊢ t1 : T1 | (T_Ref) |
Gamma; ST ⊢ ref t1 : Ref T1 |
Gamma; ST ⊢ t1 : Ref T11 | (T_Deref) |
Gamma; ST ⊢ !t1 : T11 |
Gamma; ST ⊢ t1 : Ref T11 | |
Gamma; ST ⊢ t2 : T11 | (T_Assign) |
Gamma; ST ⊢ t1 := t2 : Unit |
Reserved Notation "Gamma ';' ST '⊢' t '∈' T" (at level 40).
Inductive has_type : context → store_ty → tm → ty → Prop :=
| T_Var : ∀Gamma ST x T,
Gamma x = Some T →
Gamma; ST ⊢ (var x) ∈ T
| T_Abs : ∀Gamma ST x T11 T12 t12,
(update Gamma x T11); ST ⊢ t12 ∈ T12 →
Gamma; ST ⊢ (abs x T11 t12) ∈ (Arrow T11 T12)
| T_App : ∀T1 T2 Gamma ST t1 t2,
Gamma; ST ⊢ t1 ∈ (Arrow T1 T2) →
Gamma; ST ⊢ t2 ∈ T1 →
Gamma; ST ⊢ (app t1 t2) ∈ T2
| T_Nat : ∀Gamma ST n,
Gamma; ST ⊢ (const n) ∈ Nat
| T_Succ : ∀Gamma ST t1,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ (scc t1) ∈ Nat
| T_Pred : ∀Gamma ST t1,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ (prd t1) ∈ Nat
| T_Mult : ∀Gamma ST t1 t2,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ t2 ∈ Nat →
Gamma; ST ⊢ (mlt t1 t2) ∈ Nat
| T_If0 : ∀Gamma ST t1 t2 t3 T,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ t2 ∈ T →
Gamma; ST ⊢ t3 ∈ T →
Gamma; ST ⊢ (test0 t1 t2 t3) ∈ T
| T_Unit : ∀Gamma ST,
Gamma; ST ⊢ unit ∈ Unit
| T_Loc : ∀Gamma ST l,
l < length ST →
Gamma; ST ⊢ (loc l) ∈ (Ref (store_Tlookup l ST))
| T_Ref : ∀Gamma ST t1 T1,
Gamma; ST ⊢ t1 ∈ T1 →
Gamma; ST ⊢ (ref t1) ∈ (Ref T1)
| T_Deref : ∀Gamma ST t1 T11,
Gamma; ST ⊢ t1 ∈ (Ref T11) →
Gamma; ST ⊢ (deref t1) ∈ T11
| T_Assign : ∀Gamma ST t1 t2 T11,
Gamma; ST ⊢ t1 ∈ (Ref T11) →
Gamma; ST ⊢ t2 ∈ T11 →
Gamma; ST ⊢ (assign t1 t2) ∈ Unit
where "Gamma ';' ST '⊢' t '∈' T" := (has_type Gamma ST t T).
Hint Constructors has_type.
Inductive has_type : context → store_ty → tm → ty → Prop :=
| T_Var : ∀Gamma ST x T,
Gamma x = Some T →
Gamma; ST ⊢ (var x) ∈ T
| T_Abs : ∀Gamma ST x T11 T12 t12,
(update Gamma x T11); ST ⊢ t12 ∈ T12 →
Gamma; ST ⊢ (abs x T11 t12) ∈ (Arrow T11 T12)
| T_App : ∀T1 T2 Gamma ST t1 t2,
Gamma; ST ⊢ t1 ∈ (Arrow T1 T2) →
Gamma; ST ⊢ t2 ∈ T1 →
Gamma; ST ⊢ (app t1 t2) ∈ T2
| T_Nat : ∀Gamma ST n,
Gamma; ST ⊢ (const n) ∈ Nat
| T_Succ : ∀Gamma ST t1,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ (scc t1) ∈ Nat
| T_Pred : ∀Gamma ST t1,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ (prd t1) ∈ Nat
| T_Mult : ∀Gamma ST t1 t2,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ t2 ∈ Nat →
Gamma; ST ⊢ (mlt t1 t2) ∈ Nat
| T_If0 : ∀Gamma ST t1 t2 t3 T,
Gamma; ST ⊢ t1 ∈ Nat →
Gamma; ST ⊢ t2 ∈ T →
Gamma; ST ⊢ t3 ∈ T →
Gamma; ST ⊢ (test0 t1 t2 t3) ∈ T
| T_Unit : ∀Gamma ST,
Gamma; ST ⊢ unit ∈ Unit
| T_Loc : ∀Gamma ST l,
l < length ST →
Gamma; ST ⊢ (loc l) ∈ (Ref (store_Tlookup l ST))
| T_Ref : ∀Gamma ST t1 T1,
Gamma; ST ⊢ t1 ∈ T1 →
Gamma; ST ⊢ (ref t1) ∈ (Ref T1)
| T_Deref : ∀Gamma ST t1 T11,
Gamma; ST ⊢ t1 ∈ (Ref T11) →
Gamma; ST ⊢ (deref t1) ∈ T11
| T_Assign : ∀Gamma ST t1 t2 T11,
Gamma; ST ⊢ t1 ∈ (Ref T11) →
Gamma; ST ⊢ t2 ∈ T11 →
Gamma; ST ⊢ (assign t1 t2) ∈ Unit
where "Gamma ';' ST '⊢' t '∈' T" := (has_type Gamma ST t T).
Hint Constructors has_type.
Of course, these typing rules will accurately predict the results
of reduction only if the concrete store used during reduction
actually conforms to the store typing that we assume for purposes
of typechecking. This proviso exactly parallels the situation
with free variables in the basic STLC: the substitution lemma
promises that, if Gamma ⊢ t : T, then we can replace the free
variables in t with values of the types listed in Gamma to
obtain a closed term of type T, which, by the type preservation
theorem will reduce to a final result of type T if it yields
any result at all. We will see below how to formalize an
analogous intuition for stores and store typings.
However, for purposes of typechecking the terms that programmers
actually write, we do not need to do anything tricky to guess what
store typing we should use. Concrete locations arise only in
terms that are the intermediate results of reduction; they are
not in the language that programmers write. Thus, we can simply
typecheck the programmer's terms with respect to the empty store
typing. As reduction proceeds and new locations are created, we
will always be able to see how to extend the store typing by
looking at the type of the initial values being placed in newly
allocated cells; this intuition is formalized in the statement of
the type preservation theorem below.
Properties
- Progress — pretty much same as always
- Preservation — needs to be stated more carefully!
Well-Typed Stores
Theorem preservation_wrong1 : ∀ST T t st t' st',
empty; ST ⊢ t ∈ T →
t / st --> t' / st' →
empty; ST ⊢ t' ∈ T.
Abort.
empty; ST ⊢ t ∈ T →
t / st --> t' / st' →
empty; ST ⊢ t' ∈ T.
Abort.
Obviously wrong: no relation between assumed store typing
and provided store!
We need a way of saying "this store satisfies the assumptions of
that store typing"...
Definition store_well_typed (ST:store_ty) (st:store) :=
length ST = length st ∧
(∀l, l < length st →
empty; ST ⊢ (store_lookup l st) ∈ (store_Tlookup l ST)).
length ST = length st ∧
(∀l, l < length st →
empty; ST ⊢ (store_lookup l st) ∈ (store_Tlookup l ST)).
Informally, we will write ST ⊢ st for store_well_typed ST st.
We can now state something closer to the desired preservation
property:
Theorem preservation_wrong2 : ∀ST T t st t' st',
empty; ST ⊢ t ∈ T →
t / st --> t' / st' →
store_well_typed ST st →
empty; ST ⊢ t' ∈ T.
Abort.
empty; ST ⊢ t ∈ T →
t / st --> t' / st' →
store_well_typed ST st →
empty; ST ⊢ t' ∈ T.
Abort.
This works... for all but one of the reduction rules!
Extending Store Typings
Inductive extends : store_ty → store_ty → Prop :=
| extends_nil : ∀ST',
extends ST' nil
| extends_cons : ∀x ST' ST,
extends ST' ST →
extends (x::ST') (x::ST).
Hint Constructors extends.
| extends_nil : ∀ST',
extends ST' nil
| extends_cons : ∀x ST' ST,
extends ST' ST →
extends (x::ST') (x::ST).
Hint Constructors extends.
We'll need a few technical lemmas about extended contexts.
First, looking up a type in an extended store typing yields the
same result as in the original:
Lemma extends_lookup : ∀l ST ST',
l < length ST →
extends ST' ST →
store_Tlookup l ST' = store_Tlookup l ST.
l < length ST →
extends ST' ST →
store_Tlookup l ST' = store_Tlookup l ST.
Next, if ST' extends ST, the length of ST' is at least that
of ST.
Lemma length_extends : ∀l ST ST',
l < length ST →
extends ST' ST →
l < length ST'.
l < length ST →
extends ST' ST →
l < length ST'.
Finally, ST ++ T extends ST, and extends is reflexive.
Lemma extends_app : ∀ST T,
extends (ST ++ T) ST.
Lemma extends_refl : ∀ST,
extends ST ST.
extends (ST ++ T) ST.
Lemma extends_refl : ∀ST,
extends ST ST.
Preservation, Finally
Definition preservation_theorem := ∀ST t t' T st st',
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
t / st --> t' / st' →
∃ST',
(extends ST' ST ∧
empty; ST' ⊢ t' ∈ T ∧
store_well_typed ST' st').
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
t / st --> t' / st' →
∃ST',
(extends ST' ST ∧
empty; ST' ⊢ t' ∈ T ∧
store_well_typed ST' st').
Note that this gives us just what we need to "turn the
crank" when applying the theorem to multi-step reduction
sequences.
Substitution Lemma
Inductive appears_free_in : string → tm → Prop :=
| afi_var : ∀x,
appears_free_in x (var x)
| afi_app1 : ∀x t1 t2,
appears_free_in x t1 → appears_free_in x (app t1 t2)
| afi_app2 : ∀x t1 t2,
appears_free_in x t2 → appears_free_in x (app t1 t2)
| afi_abs : ∀x y T11 t12,
y ≠ x →
appears_free_in x t12 →
appears_free_in x (abs y T11 t12)
| afi_succ : ∀x t1,
appears_free_in x t1 →
appears_free_in x (scc t1)
| afi_pred : ∀x t1,
appears_free_in x t1 →
appears_free_in x (prd t1)
| afi_mult1 : ∀x t1 t2,
appears_free_in x t1 →
appears_free_in x (mlt t1 t2)
| afi_mult2 : ∀x t1 t2,
appears_free_in x t2 →
appears_free_in x (mlt t1 t2)
| afi_if0_1 : ∀x t1 t2 t3,
appears_free_in x t1 →
appears_free_in x (test0 t1 t2 t3)
| afi_if0_2 : ∀x t1 t2 t3,
appears_free_in x t2 →
appears_free_in x (test0 t1 t2 t3)
| afi_if0_3 : ∀x t1 t2 t3,
appears_free_in x t3 →
appears_free_in x (test0 t1 t2 t3)
| afi_ref : ∀x t1,
appears_free_in x t1 → appears_free_in x (ref t1)
| afi_deref : ∀x t1,
appears_free_in x t1 → appears_free_in x (deref t1)
| afi_assign1 : ∀x t1 t2,
appears_free_in x t1 → appears_free_in x (assign t1 t2)
| afi_assign2 : ∀x t1 t2,
appears_free_in x t2 → appears_free_in x (assign t1 t2).
Hint Constructors appears_free_in.
Lemma free_in_context : ∀x t T Gamma ST,
appears_free_in x t →
Gamma; ST ⊢ t ∈ T →
∃T', Gamma x = Some T'.
Lemma context_invariance : ∀Gamma Gamma' ST t T,
Gamma; ST ⊢ t ∈ T →
(∀x, appears_free_in x t → Gamma x = Gamma' x) →
Gamma'; ST ⊢ t ∈ T.
Lemma substitution_preserves_typing : ∀Gamma ST x s S t T,
empty; ST ⊢ s ∈ S →
(update Gamma x S); ST ⊢ t ∈ T →
Gamma; ST ⊢ ([x:=s]t) ∈ T.
| afi_var : ∀x,
appears_free_in x (var x)
| afi_app1 : ∀x t1 t2,
appears_free_in x t1 → appears_free_in x (app t1 t2)
| afi_app2 : ∀x t1 t2,
appears_free_in x t2 → appears_free_in x (app t1 t2)
| afi_abs : ∀x y T11 t12,
y ≠ x →
appears_free_in x t12 →
appears_free_in x (abs y T11 t12)
| afi_succ : ∀x t1,
appears_free_in x t1 →
appears_free_in x (scc t1)
| afi_pred : ∀x t1,
appears_free_in x t1 →
appears_free_in x (prd t1)
| afi_mult1 : ∀x t1 t2,
appears_free_in x t1 →
appears_free_in x (mlt t1 t2)
| afi_mult2 : ∀x t1 t2,
appears_free_in x t2 →
appears_free_in x (mlt t1 t2)
| afi_if0_1 : ∀x t1 t2 t3,
appears_free_in x t1 →
appears_free_in x (test0 t1 t2 t3)
| afi_if0_2 : ∀x t1 t2 t3,
appears_free_in x t2 →
appears_free_in x (test0 t1 t2 t3)
| afi_if0_3 : ∀x t1 t2 t3,
appears_free_in x t3 →
appears_free_in x (test0 t1 t2 t3)
| afi_ref : ∀x t1,
appears_free_in x t1 → appears_free_in x (ref t1)
| afi_deref : ∀x t1,
appears_free_in x t1 → appears_free_in x (deref t1)
| afi_assign1 : ∀x t1 t2,
appears_free_in x t1 → appears_free_in x (assign t1 t2)
| afi_assign2 : ∀x t1 t2,
appears_free_in x t2 → appears_free_in x (assign t1 t2).
Hint Constructors appears_free_in.
Lemma free_in_context : ∀x t T Gamma ST,
appears_free_in x t →
Gamma; ST ⊢ t ∈ T →
∃T', Gamma x = Some T'.
Lemma context_invariance : ∀Gamma Gamma' ST t T,
Gamma; ST ⊢ t ∈ T →
(∀x, appears_free_in x t → Gamma x = Gamma' x) →
Gamma'; ST ⊢ t ∈ T.
Lemma substitution_preserves_typing : ∀Gamma ST x s S t T,
empty; ST ⊢ s ∈ S →
(update Gamma x S); ST ⊢ t ∈ T →
Gamma; ST ⊢ ([x:=s]t) ∈ T.
Assignment Preserves Store Typing
Lemma assign_pres_store_typing : ∀ST st l t,
l < length st →
store_well_typed ST st →
empty; ST ⊢ t ∈ (store_Tlookup l ST) →
store_well_typed ST (replace l t st).
l < length st →
store_well_typed ST st →
empty; ST ⊢ t ∈ (store_Tlookup l ST) →
store_well_typed ST (replace l t st).
Weakening for Stores
Lemma store_weakening : ∀Gamma ST ST' t T,
extends ST' ST →
Gamma; ST ⊢ t ∈ T →
Gamma; ST' ⊢ t ∈ T.
extends ST' ST →
Gamma; ST ⊢ t ∈ T →
Gamma; ST' ⊢ t ∈ T.
We can use the store_weakening lemma to prove that if a store is
well typed with respect to a store typing, then the store extended
with a new term t will still be well typed with respect to the
store typing extended with t's type.
Lemma store_well_typed_app : ∀ST st t1 T1,
store_well_typed ST st →
empty; ST ⊢ t1 ∈ T1 →
store_well_typed (ST ++ T1::nil) (st ++ t1::nil).
store_well_typed ST st →
empty; ST ⊢ t1 ∈ T1 →
store_well_typed (ST ++ T1::nil) (st ++ t1::nil).
Preservation!
Lemma nth_eq_last : ∀A (l:list A) x d,
nth (length l) (l ++ x::nil) d = x.
nth (length l) (l ++ x::nil) d = x.
And here, at last, is the preservation theorem and proof:
Theorem preservation : ∀ST t t' T st st',
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
t / st --> t' / st' →
∃ST',
(extends ST' ST ∧
empty; ST' ⊢ t' ∈ T ∧
store_well_typed ST' st').
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
t / st --> t' / st' →
∃ST',
(extends ST' ST ∧
empty; ST' ⊢ t' ∈ T ∧
store_well_typed ST' st').
Exercise: 3 stars, standard (preservation_informal)
Write a careful informal proof of the preservation theorem, concentrating on the T_App, T_Deref, T_Assign, and T_Ref cases.Progress
Theorem progress : ∀ST t T st,
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
(value t ∨ ∃t' st', t / st --> t' / st').
empty; ST ⊢ t ∈ T →
store_well_typed ST st →
(value t ∨ ∃t' st', t / st --> t' / st').
References and Nontermination
(\r:Ref (Unit -> Unit).
r := (\x:Unit.(!r) unit); (!r) unit)
(ref (\x:Unit.unit))
First, ref (\x:Unit.unit) creates a reference to a cell of type
Unit → Unit. We then pass this reference as the argument to a
function which binds it to the name r, and assigns to it the
function \x:Unit.(!r) unit — that is, the function which ignores
its argument and calls the function stored in r on the argument
unit; but of course, that function is itself! To start the
divergent loop, we execute the function stored in the cell by
evaluating (!r) unit.
Module ExampleVariables.
Open Scope string_scope.
Definition x := "x".
Definition y := "y".
Definition r := "r".
Definition s := "s".
End ExampleVariables.
Module RefsAndNontermination.
Import ExampleVariables.
Definition loop_fun :=
abs x Unit (app (deref (var r)) unit).
Definition loop :=
app
(abs r (Ref (Arrow Unit Unit))
(tseq (assign (var r) loop_fun)
(app (deref (var r)) unit)))
(ref (abs x Unit unit)).
Open Scope string_scope.
Definition x := "x".
Definition y := "y".
Definition r := "r".
Definition s := "s".
End ExampleVariables.
Module RefsAndNontermination.
Import ExampleVariables.
Definition loop_fun :=
abs x Unit (app (deref (var r)) unit).
Definition loop :=
app
(abs r (Ref (Arrow Unit Unit))
(tseq (assign (var r) loop_fun)
(app (deref (var r)) unit)))
(ref (abs x Unit unit)).
This term is well typed:
Lemma loop_typeable : ∃T, empty; nil ⊢ loop ∈ T.
To show formally that the term diverges, we first define the
step_closure of the single-step reduction relation, written
-->+. This is just like the reflexive step closure of
single-step reduction (which we're been writing -->*), except
that it is not reflexive: t -->+ t' means that t can reach
t' by one or more steps of reduction.
Inductive step_closure {X:Type} (R: relation X) : X → X → Prop :=
| sc_one : ∀(x y : X),
R x y → step_closure R x y
| sc_step : ∀(x y z : X),
R x y →
step_closure R y z →
step_closure R x z.
Definition multistep1 := (step_closure step).
Notation "t1 '/' st '-->+' t2 '/' st'" :=
(multistep1 (t1,st) (t2,st'))
(at level 40, st at level 39, t2 at level 39).
| sc_one : ∀(x y : X),
R x y → step_closure R x y
| sc_step : ∀(x y z : X),
R x y →
step_closure R y z →
step_closure R x z.
Definition multistep1 := (step_closure step).
Notation "t1 '/' st '-->+' t2 '/' st'" :=
(multistep1 (t1,st) (t2,st'))
(at level 40, st at level 39, t2 at level 39).
Now, we can show that the expression loop reduces to the
expression !(loc 0) unit and the size-one store
[r:=(loc 0)]loop_fun.
As a convenience, we introduce a slight variant of the normalize
tactic, called reduce, which tries solving the goal with
multi_refl at each step, instead of waiting until the goal can't
be reduced any more. Of course, the whole point is that loop
doesn't normalize, so the old normalize tactic would just go
into an infinite loop reducing it forever!
Ltac print_goal := match goal with ⊢ ?x ⇒ idtac x end.
Ltac reduce :=
repeat (print_goal; eapply multi_step ;
[ (eauto 10; fail) | (instantiate; compute)];
try solve [apply multi_refl]).
Ltac reduce :=
repeat (print_goal; eapply multi_step ;
[ (eauto 10; fail) | (instantiate; compute)];
try solve [apply multi_refl]).
Next, we use reduce to show that loop steps to
!(loc 0) unit, starting from the empty store.
Lemma loop_steps_to_loop_fun :
loop / nil -->*
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil.
Proof.
unfold loop.
reduce.
Qed.
loop / nil -->*
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil.
Proof.
unfold loop.
reduce.
Qed.
Finally, we show that the latter expression reduces in
two steps to itself!
Lemma loop_fun_step_self :
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil -->+
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil.
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil -->+
app (deref (loc 0)) unit / cons ([r:=loc 0]loop_fun) nil.
Exercise: 4 stars, standard (factorial_ref)
Use the above ideas to implement a factorial function in STLC with references. (There is no need to prove formally that it really behaves like the factorial. Just uncomment the example below to make sure it gives the correct result when applied to the argument 4.)
Definition factorial : tm
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Lemma factorial_type : empty; nil ⊢ factorial ∈ (Arrow Nat Nat).
Proof with eauto.
(* FILL IN HERE *) Admitted.
(* REPLACE THIS LINE WITH ":= _your_definition_ ." *). Admitted.
Lemma factorial_type : empty; nil ⊢ factorial ∈ (Arrow Nat Nat).
Proof with eauto.
(* FILL IN HERE *) Admitted.
If your definition is correct, you should be able to just
uncomment the example below; the proof should be fully
automatic using the reduce tactic.
(*
Lemma factorial_4 : exists st,
app factorial (const 4) / nil -->* const 24 / st.
Proof.
eexists. unfold factorial. reduce.
Qed.
*)
☐
Lemma factorial_4 : exists st,
app factorial (const 4) / nil -->* const 24 / st.
Proof.
eexists. unfold factorial. reduce.
Qed.
*)
Additional Exercises
Exercise: 5 stars, standard, optional (garabage_collector)
Challenge problem: modify our formalization to include an account of garbage collection, and prove that it satisfies whatever nice properties you can think to prove about it.
End RefsAndNontermination.
End STLCRef.
End STLCRef.