Require Export lecture2.
Require Import EqNat.
(* # Contents *)
(*
In the next lecture, we will be defining a program logic in the form of a
separation logic for our ML-like language. The crucial feature of separation
logic is that it allows one to talk about _disjointness_ of memories. In this
part of the lecture, we will prepare for the next lecture by defining the
following operations on maps, and proving properties about them:
munion : map A -> map A -> map A
mdisjoint : map A -> map A -> Prop
A crucial thing that you will learn now is reuse existing lemmas on finite maps
to prove derived properties.
*)
(* # The union operation on finite maps *)
(*
In order to model separation logic in Coq, we need to have an operation that
takes the union of two finite maps:
munion : map A -> map A -> map A
That satisfies
mlookup (munion m1 m2) i =
option_union (mlookup m1 i) (mlookup m2 i)
Where `option_union` is the (left-biased) union operation on the `option` type,
which is defined as follows:
*)
Definition option_union {A} (mx my : option A) : option A :=
match mx with
| Some x => Some x
| None => my
end.
(*
As is usual, we define the union operation in several stages: 1.) we define it
on raw maps, 2.) we prove that it preserves well-formedness of maps, 3.) lift
the operation to maps (which are bundled with a proof of well-formedness) 4.) we
prove the lookup property on raw maps, and finally 5.) lift that property from
raw maps to maps.
*)
Fixpoint munion_raw {A} (m1 m2 : map_raw A) : map_raw A :=
match m1, m2 with
| mx :: m1, my :: m2 => option_union mx my :: munion_raw m1 m2
| [], m2 => m2
| m1, [] => m1
end.
Lemma munion_raw_wf {A} b (m1 m2 : map_raw A) :
map_wf b m1 -> map_wf b m2 -> map_wf b (munion_raw m1 m2).
Proof. (* left as an optional exercise *) Admitted.
Lemma munion_lookup_raw {A} (m1 m2 : map_raw A) (i : nat) :
mlookup_raw (munion_raw m1 m2) i =
option_union (mlookup_raw m1 i) (mlookup_raw m2 i).
Proof. (* left as an optional exercise *) Admitted.
Definition munion {A} (m1 m2 : map A) : map A :=
let (m1,Hm1) := m1 in
let (m2,Hm2) := m2 in
make_map (munion_raw m1 m2) (munion_raw_wf _ m1 m2 Hm1 Hm2).
Lemma munion_lookup {A} (m1 m2 : map A) (i : nat) :
mlookup (munion m1 m2) i =
option_union (mlookup m1 i) (mlookup m2 i).
Proof. (* left as an optional exercise *) Admitted.
(* ## Properties of the union operation *)
(*
Now that we have defined the operation `munion` and have proven the lemma
`munion_lookup`, we prove some properties. The crucial thing to notice is that
the lemma `munion_lookup` fully specifies the behavior of the union operation,
after we have proven that, we never have to unfold the definition again. Let us
take a look at an example.
*)
Lemma munion_empty_l {A} (m : map A) : munion mempty m = m.
Proof.
(* Note that apply also works with bi-implications, like `map_eq`:
m1 = m2 <-> (forall i : nat, mlookup m1 i = mlookup m2 i)
*)
apply map_eq. intros i.
rewrite munion_lookup.
rewrite mempty_lookup.
simpl.
reflexivity.
Qed.
Lemma munion_insert_l {A} (m : map A) i x :
minsert i x m = munion (msingleton i x) m.
Proof.
apply map_eq. intros j. rewrite munion_lookup.
(* In order to proceed, we need to make a case distinction between whether
the natural numbers `i` and `j` are equal or not. For this we use the lemma:
Nat.eq_dec: forall n m : nat, {n = m} + {n <> m}
The lemma says that for any two natural numbers, we have either a proof
of `x = y` that they are equal, or a proof of `n <> m` that they are unequal.
The result of the lemma is a `sumbool`:
Inductive sumbool (A B : Prop) : Set :=
left : A -> {A} + {B} | right : B -> {A} + {B}
On which we will perform a case analysis using the `destruct` tactic.
*)
destruct (Nat.eq_dec i j) as [Heq|Hneq].
- subst i.
rewrite minsert_lookup. rewrite msingleton_lookup.
simpl. reflexivity.
- (* In this case, we will use the lemma `insert_lookup_ne`:
i <> j -> mlookup (minsert i y m) j = mlookup m j
Note that contrary to most lemmas we have used for rewriting so far, this
lemma has a premise `i <> j`. As a result, when we say:
rewrite minsert_lookup_ne
We will get an additional goal for proving the premise `i <> j`. In this
case, however, it is trivial to prove the premise `i <> j`, after all, we
already have a hypothesis `Hneq : i <> j`. As such, we can make use of the
`by` argument of the `rewrite` tactic as follows:
*)
rewrite minsert_lookup_ne by assumption.
(*
This causes the `assumption` tactic to be used for proving the premise of
the lemma that we wish to rewrite with.
*)
rewrite msingleton_lookup_ne by assumption.
(*
Note that `msingleton_lookup_ne` has a similar premise.
*)
simpl. reflexivity.
Qed.
(* ### Exercise *)
(*
Prove the following properties. In order to prove the properties about maps, you
should make use of the lemma `map_eq`, and the lookup lemmas for the various
operations involved.
*)
Lemma munion_empty_r {A} (m : map A) : munion mempty m = m.
Proof. Admitted.
Lemma option_union_assoc {A} (mx my mz : option A) :
option_union mx (option_union my mz) = option_union (option_union mx my) mz.
Proof. Admitted.
Lemma munion_assoc {A} (m1 m2 m3 : map A) :
munion m1 (munion m2 m3) = munion (munion m1 m2) m3.
Proof. Admitted.
Lemma minsert_union {A} (m1 m2 : map A) i x :
minsert i x (munion m1 m2) = munion (minsert i x m1) m2.
Proof. Admitted.
Lemma mdelete_union {A} (m1 m2 : map A) i :
mdelete i (munion m1 m2) = munion (mdelete i m1) (mdelete i m2).
Proof. Admitted.
(* ## Disjointness of finite maps *)
(*
So far, we have proved associativity of the union operation on finite maps, but
not yet commutativity. Unfortunately, this property does not hold
unconditionally. In case `m1` and `m2` both contain the key `i`, but with
different values, we do not have `munion m1 m2 = munion m2 m1`. To deal with
this issue, we define a relation:
mdisjoint : map A -> map A -> Prop
Which states that two maps are _disjoint_, i.e. they do not have any keys in
common. We can then state commutativity as:
mdisjoint m1 m2 -> munion m1 m2 = munion m2 m1.
*)
Definition mdisjoint {A} (m1 m2 : map A) : Prop :=
forall i, mlookup m1 i = None \/ mlookup m2 i = None.
Lemma option_union_None_r {A} (mx : option A) : option_union mx None = mx.
Proof. destruct mx; simpl; reflexivity. Qed.
Lemma munion_comm {A} (m1 m2 : map A) :
mdisjoint m1 m2 -> munion m1 m2 = munion m2 m1.
Proof.
intros Hdisj. apply map_eq; intros i.
rewrite !munion_lookup. (* Using the modifier `!` of the rewrite tactic, we
can rewrite using a lemma as many times as possible. *)
destruct (Hdisj i) as [Hi | Hi].
- rewrite Hi. simpl. rewrite option_union_None_r. reflexivity.
- rewrite Hi. simpl. rewrite option_union_None_r. reflexivity.
Qed.
(*
Let us prove some more properties about disjointness of finite maps.
*)
Lemma mdisjoint_empty_l {A} (m : map A) : mdisjoint mempty m.
Proof.
intros i. left.
rewrite mempty_lookup. reflexivity.
Qed.
Lemma mdisjoint_sym {A} (m1 m2 : map A) : mdisjoint m1 m2 -> mdisjoint m2 m1.
Proof.
intros Hm i.
unfold mdisjoint in Hm.
(* As we see now, we have a hypothesis
Hm : forall i : nat, mlookup m1 i = None \/ mlookup m2 i = None
Which contains a disjunction below a universal quantifier. As we have seen
before, we can use the `destruct` tactic to eliminate disjunctions. But how
can we deal with disjunctions below a universal quantifier?
Well, the answer is simple: we first have to instantiate the universal
quantifier. But how do we do that? By the Curry-Howard correspondence, we
know that `forall` quantifiers are in fact dependent functions, which means
that when we have `H : forall x : A, P x`, we just write `H a` to obtain
something of type `P a`.
So, in this case, we have:
Hm i : mlookup m1 i = None \/ mlookup m2 i = None
On which we can then do a case analysis.
*)
destruct (Hm i) as [Hm1|Hm2].
- right. assumption.
- left. assumption.
Qed.
(* ### Exercise *)
(* Prove the properties below. Do not forget that you can use
destruct (Nat.eq_dec i j) as [Heq|Hneq].
To make a case analysis between whether `i` and `j` are equal or not.
*)
Lemma mdisjoint_singleton {A} (m : map A) i x :
mlookup m i = None -> mdisjoint (msingleton i x) m.
Proof. Admitted.
Lemma mdisjoint_singleton_inv {A} (m : map A) i x :
mdisjoint (msingleton i x) m -> mlookup m i = None.
Proof. Admitted.
Lemma mdisjoint_union_l {A} (m1 m2 m3 : map A) :
mdisjoint m1 m3 ->
mdisjoint m2 m3 ->
mdisjoint (munion m1 m2) m3.
Proof. Admitted.
Lemma mdisjoint_union_inv_ll {A} (m1 m2 m3 : map A) :
mdisjoint (munion m1 m2) m3 ->
mdisjoint m1 m3.
Proof. Admitted.
Lemma mdisjoint_union_inv_lr {A} (m1 m2 m3 : map A) :
mdisjoint (munion m1 m2) m3 ->
mdisjoint m2 m3.
Proof. Admitted.
(*
Note that the properties below can be derived from the properties you have just
proven. You should not unfold the definition `mdisjoint`.
*)
Lemma mdisjoint_union_r {A} (m1 m2 m3 : map A) :
mdisjoint m3 m1 ->
mdisjoint m3 m2 ->
mdisjoint m3 (munion m1 m2).
Proof. Admitted.
Lemma mdisjoint_union_inv_rl {A} (m1 m2 m3 : map A) :
mdisjoint m3 (munion m1 m2) ->
mdisjoint m3 m1.
Proof. Admitted.
Lemma mdisjoint_union_inv_rr {A} (m1 m2 m3 : map A) :
mdisjoint m3 (munion m1 m2) ->
mdisjoint m3 m2.
Proof. Admitted.
(* ### Exercise *)
(*
And finally, some more properties about finite maps that will need later.
*)
Lemma minsert_singleton {A} i (x y : A) :
minsert i x (msingleton i y) = msingleton i x.
Proof. Admitted.
Lemma mdelete_singleton {A} i (x : A) :
mdelete i (msingleton i x) = mempty.
Proof. Admitted.
Lemma mdelete_None {A} (m : map A) i :
mlookup m i = None -> mdelete i m = m.
Proof. Admitted.