exercises.linked_lists
From iris.heap_lang Require Import lang proofmode notation.
(* ################################ *)
(* ################################ *)
In this chapter, we will study several functions on linked lists. To
do this, we must first agree on what a linked list is. In HeapLang, we
can implement linked lists as chains of pointers. We define this
formally with a predicate, which we denote isList. This predicate
turns a list of values xs into a predicate describing the structure
of the linked list.
(* To Mansky, l is the head of xs? *)
Fixpoint isList (l : val) (xs : list val) : iProp Σ :=
match xs with
| [] => ⌜l = NONEV⌝
| x :: xs => ∃ (hd : loc) l', ⌜l = SOMEV (#hd)⌝ ∗ hd ↦ (x, l') ∗ isList l' xs
end.
Fixpoint isList (l : val) (xs : list val) : iProp Σ :=
match xs with
| [] => ⌜l = NONEV⌝
| x :: xs => ∃ (hd : loc) l', ⌜l = SOMEV (#hd)⌝ ∗ hd ↦ (x, l') ∗ isList l' xs
end.
Here, NONEV and SOMEV v are the value equivalents of NONE and
SOME e.
We can now define HeapLang functions that act on lists, such as inc.
The inc function recursively increments all the values of a list.
Definition inc : val :=
rec: "inc" "l" :=
match: "l" with
NONE => #()
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"hd" <- ("x" + #1, "l'");;
"inc" "l'"
end.
rec: "inc" "l" :=
match: "l" with
NONE => #()
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"hd" <- ("x" + #1, "l'");;
"inc" "l'"
end.
Intuitively, the specification we give for this function should state
that the linked list should only contain integers and that, after
executing the function, each integer has been incremented. As such, we
parametrise the specification not by a list of values, but by a list
of integers. We then map each integer to a HeapLang value using # _,
allowing us to use the isList predicate.
(* To Mansky, I do not understand <*)
(* *)
(* Print fmap. *)
(* Print map. a special case of fmap on list *)
Lemma inc_spec (l : val) (xs : list Z) :
{{{ isList l ((λ x : Z, #x) <$> xs) }}}
inc l
{{{ RET #(); isList l ((λ x, #(x + 1)) <$> xs)}}}.
Proof.
(* redo *)
(*
revert l.
induction xs.
- intros.
iIntros "P Q".
simpl.
iDestruct "P" as "hd %l' C]".
iDestruct "C" as "(*)
(* *)
(* Print fmap. *)
(* Print map. a special case of fmap on list *)
Lemma inc_spec (l : val) (xs : list Z) :
{{{ isList l ((λ x : Z, #x) <$> xs) }}}
inc l
{{{ RET #(); isList l ((λ x, #(x + 1)) <$> xs)}}}.
Proof.
(* redo *)
(*
revert l.
induction xs.
- intros.
iIntros "P Q".
simpl.
iDestruct "P" as "hd %l' C]".
iDestruct "C" as "(*)
The proof proceeds by structural induction in xs. As l changes in each
iteration, we must universally quantify over it to strengthen the induction
hypothesis.
revert l.
induction xs as [|x xs' IH].
- (* Base Case: xs = *)
(*
iIntros (l) "HA". iIntros "HΦ".
Σ : gFunctors
heapGS0 : heapGS Σ
l : val
Φ : val → iPropI Σ
HA : l = InjLV () (1 / 1) "HΦ" : ▷ (⌜l = InjLV ()⌝ -∗ Φ ()) --------------------------------------∗ WP inc l {{ v, Φ v }} *)
iIntros (l) "%Φ -> HΦ".
wp_rec.
wp_pures.
by iApply "HΦ".
- (* Induction step: xs = x :: xs' *)
(* exercise *)
(* the head is inc by 1 by inc definition, and rest by IH *)
iIntros "%l %Φ HA HΦ".
simpl.
iDestruct "HA" as "(%ehd & %tail & %leqhd & ehd_defef & AAA)".
(* iDestruct "HA" as "%ehd [%tail (%leqhd & ehd_defef & AAA)]". *)
wp_rec.
rewrite -> leqhd.
wp_pure.
wp_pure.
wp_pures.
wp_load.
wp_pures.
wp_load.
wp_pures.
wp_store.
iApply (IH with "[AAA]").
+ iApply "AAA".
+ iNext.
iIntros "BBB".
iApply "HΦ".
iExists ehd.
iExists tail.
iFrame.
done.
Qed.
induction xs as [|x xs' IH].
- (* Base Case: xs = *)
(*
iIntros (l) "HA". iIntros "HΦ".
Σ : gFunctors
heapGS0 : heapGS Σ
l : val
Φ : val → iPropI Σ
HA : l = InjLV () (1 / 1) "HΦ" : ▷ (⌜l = InjLV ()⌝ -∗ Φ ()) --------------------------------------∗ WP inc l {{ v, Φ v }} *)
iIntros (l) "%Φ -> HΦ".
wp_rec.
wp_pures.
by iApply "HΦ".
- (* Induction step: xs = x :: xs' *)
(* exercise *)
(* the head is inc by 1 by inc definition, and rest by IH *)
iIntros "%l %Φ HA HΦ".
simpl.
iDestruct "HA" as "(%ehd & %tail & %leqhd & ehd_defef & AAA)".
(* iDestruct "HA" as "%ehd [%tail (%leqhd & ehd_defef & AAA)]". *)
wp_rec.
rewrite -> leqhd.
wp_pure.
wp_pure.
wp_pures.
wp_load.
wp_pures.
wp_load.
wp_pures.
wp_store.
iApply (IH with "[AAA]").
+ iApply "AAA".
+ iNext.
iIntros "BBB".
iApply "HΦ".
iExists ehd.
iExists tail.
iFrame.
done.
Qed.
The append function recursively descends l1, updating the links.
Eventually, it reaches the tail, NONE, where it will replace it with
l2.
Definition append : val :=
rec: "append" "l1" "l2" :=
match: "l1" with
NONE => "l2"
| SOME "hd" =>
let: "x" := Fst (!"hd") in
let: "l1'" := Snd (!"hd") in
let: "r" := "append" "l1'" "l2" in
"hd" <- ("x", "r");;
SOME "hd"
end.
rec: "append" "l1" "l2" :=
match: "l1" with
NONE => "l2"
| SOME "hd" =>
let: "x" := Fst (!"hd") in
let: "l1'" := Snd (!"hd") in
let: "r" := "append" "l1'" "l2" in
"hd" <- ("x", "r");;
SOME "hd"
end.
If l1 and l2 represent the lists xs and ys respectively, then
we expect that append l1 l2 will return a list representing
xs ++ ys.
Lemma append_spec (l1 l2 : val) (xs ys : list val) :
{{{ isList l1 xs ∗ isList l2 ys }}}
append l1 l2
{{{ l, RET l; isList l (xs ++ ys) }}}.
Proof.
(*redo*)
(*
revert ys l1 l2.
induction xs.
- intros. iIntros "-> l2_is_ys post".
simpl.
wp_rec.
wp_let.
wp_match.
by iApply "post".
- intros. iIntros "l1_is_a_xs l2_is_ys post".
wp_rec.
wp_let.
simpl.
iDestruct "l1_is_a_xs" as "(l' & -> & hd_pt & l'_is_xs)".
wp_match.
wp_load.
wp_proj.
wp_let.
wp_load.
wp_pures.
wp_apply (IHxs with "l'_is_xs l2_is_ys hd_pt post").
+ iFrame.
+ iNext.
iIntros "*)
revert ys l1 l2.
induction xs as [| x xs' IH]; simpl.
(* exercise *)
- iIntros "%ys %l1 %l2 %Φ".
iIntros "[%l1isNull l2isys] post".
rewrite l1isNull.
unfold append.
wp_rec.
wp_let.
wp_pures.
iApply ("post" with "l2isys").
- iIntros (ys l1 l2 Φ).
iIntros "[(%hd & %l' & l1eqhd & hdptxl' & l'xs'islist) BBB] CCC".
(* iDestruct "AAA" as "".
iIntros "AAA BBB CCC".
iDestruct "AAA" as "(l' & l1eqhd & hdptxl' & l'xs'islist)". *)
wp_rec.
wp_let.
iDestruct "l1eqhd" as %l1eqhd.
rewrite l1eqhd.
wp_pures.
wp_load.
wp_proj.
wp_let.
wp_load.
wp_pures.
(* each is for one branch *)
wp_apply (IH with "[l'xs'islist BBB] [hdptxl' CCC]").
+ iFrame.
+ iNext.
iIntros "%l isList_l_xs'ys".
wp_let.
wp_store.
wp_pure.
iApply "CCC".
iExists hd.
iExists l.
iFrame.
done.
Qed.
{{{ isList l1 xs ∗ isList l2 ys }}}
append l1 l2
{{{ l, RET l; isList l (xs ++ ys) }}}.
Proof.
(*redo*)
(*
revert ys l1 l2.
induction xs.
- intros. iIntros "-> l2_is_ys post".
simpl.
wp_rec.
wp_let.
wp_match.
by iApply "post".
- intros. iIntros "l1_is_a_xs l2_is_ys post".
wp_rec.
wp_let.
simpl.
iDestruct "l1_is_a_xs" as "(l' & -> & hd_pt & l'_is_xs)".
wp_match.
wp_load.
wp_proj.
wp_let.
wp_load.
wp_pures.
wp_apply (IHxs with "l'_is_xs l2_is_ys hd_pt post").
+ iFrame.
+ iNext.
iIntros "*)
revert ys l1 l2.
induction xs as [| x xs' IH]; simpl.
(* exercise *)
- iIntros "%ys %l1 %l2 %Φ".
iIntros "[%l1isNull l2isys] post".
rewrite l1isNull.
unfold append.
wp_rec.
wp_let.
wp_pures.
iApply ("post" with "l2isys").
- iIntros (ys l1 l2 Φ).
iIntros "[(%hd & %l' & l1eqhd & hdptxl' & l'xs'islist) BBB] CCC".
(* iDestruct "AAA" as "".
iIntros "AAA BBB CCC".
iDestruct "AAA" as "(l' & l1eqhd & hdptxl' & l'xs'islist)". *)
wp_rec.
wp_let.
iDestruct "l1eqhd" as %l1eqhd.
rewrite l1eqhd.
wp_pures.
wp_load.
wp_proj.
wp_let.
wp_load.
wp_pures.
(* each is for one branch *)
wp_apply (IH with "[l'xs'islist BBB] [hdptxl' CCC]").
+ iFrame.
+ iNext.
iIntros "%l isList_l_xs'ys".
wp_let.
wp_store.
wp_pure.
iApply "CCC".
iExists hd.
iExists l.
iFrame.
done.
Qed.
We will implement reverse using a helper function called
reverse_append, which takes two arguments, l and acc, and
returns the list rev l ++ acc.
Definition reverse_append : val :=
rec: "reverse_append" "l" "acc" :=
match: "l" with
NONE => "acc"
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"hd" <- ("x", "acc");;
"reverse_append" "l'" "l"
end.
rec: "reverse_append" "l" "acc" :=
match: "l" with
NONE => "acc"
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"hd" <- ("x", "acc");;
"reverse_append" "l'" "l"
end.
When acc is the empty list, it should thus simply return the reverse
of l.
Definition reverse : val :=
λ: "l", reverse_append "l" NONE.
Lemma reverse_append_spec (l acc : val) (xs ys : list val) :
{{{ isList l xs ∗ isList acc ys }}}
reverse_append l acc
{{{ v, RET v; isList v (rev xs ++ ys) }}}.
Proof.
revert l acc ys.
induction xs as [| x xs' IH]; simpl.
(* exercise *)
- intros. iIntros "[-> acc_is_ys] post".
wp_rec. wp_pures. by iApply "post".
- intros.
iIntros "A post".
iDestruct "A" as "[B C]".
iDestruct "B" as "[%hd [%l' (-> & E & F)]]".
wp_rec.
wp_let.
wp_pures.
wp_load.
wp_pures.
wp_load.
wp_pures.
wp_store.
(* To Mansky: I learnt (x :: ys) from the solution,
but do not know exactly why.
What is the next val to evalate?
How to reduce InjRV hd? *)
wp_apply (IH l' (InjRV #hd) (x :: ys) with "[F C E] [post]").
+ iFrame. done.
+ iNext.
rewrite <- app_assoc.
done.
Qed.
λ: "l", reverse_append "l" NONE.
Lemma reverse_append_spec (l acc : val) (xs ys : list val) :
{{{ isList l xs ∗ isList acc ys }}}
reverse_append l acc
{{{ v, RET v; isList v (rev xs ++ ys) }}}.
Proof.
revert l acc ys.
induction xs as [| x xs' IH]; simpl.
(* exercise *)
- intros. iIntros "[-> acc_is_ys] post".
wp_rec. wp_pures. by iApply "post".
- intros.
iIntros "A post".
iDestruct "A" as "[B C]".
iDestruct "B" as "[%hd [%l' (-> & E & F)]]".
wp_rec.
wp_let.
wp_pures.
wp_load.
wp_pures.
wp_load.
wp_pures.
wp_store.
(* To Mansky: I learnt (x :: ys) from the solution,
but do not know exactly why.
What is the next val to evalate?
How to reduce InjRV hd? *)
wp_apply (IH l' (InjRV #hd) (x :: ys) with "[F C E] [post]").
+ iFrame. done.
+ iNext.
rewrite <- app_assoc.
done.
Qed.
Now, we use the specification of reverse_append to prove the
specification of reverse.
Lemma reverse_spec (l : val) (xs : list val) :
{{{ isList l xs }}}
reverse l
{{{ v, RET v; isList v (rev xs) }}}.
Proof.
(* exercise *)
unfold reverse.
intros.
iIntros "l_is_xs post".
wp_lam.
simpl.
(*
Check reverse_append_spec.
∀ (l acc : val) (xs ys : list val) (Φ : val → iPropI Σ),
isList l xs ∗ isList acc ys -∗
▷ (∀ v : val, isList v (rev xs ++ ys) -∗ Φ v) -∗
WP reverse_append l acc {{ v, Φ v }}
*)
wp_pure.
wp_apply (reverse_append_spec l (InjLV #()) xs [] with "[l_is_xs]").
- iFrame. done.
- replace (rev xs ++ []) with (rev xs).
+ auto.
+ (*Search (?x ++ = ?x). *)
rewrite app_nil_r.
reflexivity.
Qed.
{{{ isList l xs }}}
reverse l
{{{ v, RET v; isList v (rev xs) }}}.
Proof.
(* exercise *)
unfold reverse.
intros.
iIntros "l_is_xs post".
wp_lam.
simpl.
(*
Check reverse_append_spec.
∀ (l acc : val) (xs ys : list val) (Φ : val → iPropI Σ),
isList l xs ∗ isList acc ys -∗
▷ (∀ v : val, isList v (rev xs ++ ys) -∗ Φ v) -∗
WP reverse_append l acc {{ v, Φ v }}
*)
wp_pure.
wp_apply (reverse_append_spec l (InjLV #()) xs [] with "[l_is_xs]").
- iFrame. done.
- replace (rev xs ++ []) with (rev xs).
+ auto.
+ (*Search (?x ++ = ?x). *)
rewrite app_nil_r.
reflexivity.
Qed.
The specifications thus far have been rather straightforward. Now we
will show a very general specification for fold_right.
(* apply each element of l to v, from right *)
Definition fold_right : val :=
rec: "fold_right" "f" "v" "l" :=
match: "l" with
NONE => "v"
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"f" "x" ("fold_right" "f" "v" "l'")
end.
Definition fold_right : val :=
rec: "fold_right" "f" "v" "l" :=
match: "l" with
NONE => "v"
| SOME "hd" =>
let: "x" := Fst (! "hd") in
let: "l'" := Snd (! "hd") in
"f" "x" ("fold_right" "f" "v" "l'")
end.
The following specification has a lot of moving parts, so let us go
through them one by one.
Note that Hoare triples are persistent, and persistent predicates are
closed under universal quantification. Hence, in the proof, the
assumption for f will move into the persistent context.
- l is a linked list representing xs, as seen by isList l xs in the precondition.
- P is a predicate which all the values in xs should satisfy. This is written as [∗ list] x ∈ xs, P x. It is a recursively defined predicate, defined as follows: [∗ list] x ∈ [], P x := True [∗ list] x ∈ x0 :: xs, P x := P x0 ∗ [∗ list] x ∈ xs, P x
- I is a predicate (think I for invariant) describing the relation between a list and the result of the fold.
- a is the base value, so fold will return a for the empty list; this is captured by I [] a.
- f is the folding function, which we assume satisfies: {{{ P x ∗ I ys a' }}} f x a' {{{ r, RET; I (x :: ys) r }}}. Intuitively, this says that if f is applied to an argument from the list (hence satisfying P) and a' is the result of folding ys, captured by I ys a', then the result r will be the result of folding f over x :: ys, captured by I (x :: ys) r in the postcondition.
- The result r of folding must then satisfy I xs r.
- Importantly, we do not change the original list. So we put isList l xs in the postcondition.
(*To Mansky, what is * list and ∈ ? Skipping it. *)
Lemma fold_right_spec P I (f a l : val) xs :
{{{
isList l xs ∗ ([∗ list] x ∈ xs, P x) ∗ I [] a ∗
(∀ (x a' : val) ys,
{{{ P x ∗ I ys a' }}} f x a' {{{ r, RET r; I (x :: ys) r }}})
}}}
fold_right f a l
{{{ r, RET r; isList l xs ∗ I xs r}}}.
Proof.
revert a l.
induction xs as [|x xs IHxs].
all: simpl.
(* exercise *)
Admitted.
We can now sum over a list simply by folding an addition function over
it.
Definition sum_list : val :=
λ: "l",
let: "f" := (λ: "x" "y", "x" + "y") in
fold_right "f" #0 "l".
Lemma sum_list_spec l xs :
{{{isList l ((λ x : Z, #x) <$> xs)}}}
sum_list l
{{{RET #(foldr Z.add 0 xs); isList l ((λ x : Z, #x) <$> xs)}}}.
Proof.
iIntros (Φ) "Hl HΦ".
wp_rec; wp_pures.
wp_apply (fold_right_spec
(λ x, ∃ i : Z, ⌜x = #i⌝)%I
(λ xs y, ∃ ys, ⌜xs = (λ x : Z, #x) <$> ys⌝ ∗ ⌜y = #(foldr Z.add 0 ys)⌝)%I
with "[Hl]"
).
- iFrame.
repeat iSplit.
+ iPureIntro; simpl.
intros k v Hk.
rewrite list_lookup_fmap in Hk.
destruct (xs !! k) as [x|]; last done.
injection Hk as <-.
by exists x.
+ by iExists [].
+ clear.
iIntros (x a' ys Φ) "!> [[%i ->] (%xs & -> & ->)] HΦ".
wp_pures.
iModIntro.
iApply "HΦ".
by iExists (i :: xs).
- iIntros "%_ (Hl & %ys & %H & ->)".
assert (Hinj:Inj eq eq (λ x : Z, #x)) by congruence.
apply (inj _) in H.
subst ys.
by iApply "HΦ".
Qed.
End linked_lists.