-
Notifications
You must be signed in to change notification settings - Fork 46
/
accumulator.v
153 lines (130 loc) · 6.06 KB
/
accumulator.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
(* begin hide *)
(**********************************************************************)
(* Equations *)
(* Copyright (c) 2009-2021 Matthieu Sozeau <[email protected]> *)
(**********************************************************************)
(* This file is distributed under the terms of the *)
(* GNU Lesser General Public License Version 2.1 *)
(**********************************************************************)
(* end hide *)
(** * Working with accumulators
A standard pattern of functional programming involves writting
tail-recursive versions of an algorithm using an accumulator (also
called wrapper/worker), we show how Equations makes this pattern easy
to express and reason about using where clauses, well-founded
recursion and function eliminators. *)
From Equations Require Import Equations.
From Stdlib Require Import List Syntax Arith Lia.
Import ListNotations.
(** ** Worker/wrapper
The most standard example is an efficient implementation of list reversal.
Instead of growing the stack by the size of the list, we accumulate a
partially reverted list as a new argument of our function.
We implement this using a [go] auxilliary function defined recursively
and pattern matching on the list.
*)
Equations rev_acc {A} (l : list A) : list A :=
rev_acc l := go [] l
where go : list A -> list A -> list A :=
go acc [] := acc;
go acc (hd :: tl) := go (hd :: acc) tl.
(** A typical issue with such accumulating functions is that one has to
write lemmas in two versions, once about the internal [go] function
and then on its wrapper. Using the functional elimination principle
associated to [rev_acc], we can show both properties simultaneously. *)
Lemma rev_acc_eq : forall {A} (l : list A), rev_acc l = rev l.
Proof.
(** We apply functional elimination on the [rev_acc l] call.
The eliminator expects two predicates:
one specifying the wrapper and another for the worker.
For the wrapper, we give the expected final goal but for the worker
we have to invent a kind of loop invariant: here that the result of
the whole [go acc l] call is equal to [rev l ++ acc]. *)
apply (rev_acc_elim (fun A l revaccl => revaccl = rev l)
(fun A _ acc l go_res => go_res = rev l ++ acc)); intros; simpl.
(** Functional elimination provides us with the worker property for the
initial [go [] l] call, i.e. that it is equal to [rev l ++ []], hence
the proof: *)
+ now rewrite app_nil_r in H.
(** For the worker proofs themselves, we use standard reasoning. *)
+ reflexivity.
+ now rewrite H, <- app_assoc.
Qed.
(** ** The worker/wrapper and well-founded recursion
Sometimes the natural expression of an algorithm in the worker/wrapper
pattern requires well-founded recursion: here we take an example
algorithm from Haskell whose termination is justified by a measure.
Note that the [worker] subprogram's termination measure and
implementation depends on the enclosing [k] argument which is captured
in the where clause.
*)
Obligation Tactic := idtac.
Equations? isPrime (n : nat) : bool :=
isPrime 0 := false;
isPrime 1 := false;
isPrime 2 := true;
isPrime 3 := true;
isPrime k := worker 2
where worker (n' : nat) : bool by wf (k - n') lt :=
worker n' with ge_dec n' k :=
{ | left H := true;
| right H := if Nat.eqb (Nat.modulo k n') 0 then false else worker (S n') }.
Proof. lia. Defined.
(* Require Import ExtrOcamlBasic. *)
(* Extraction isPrime. *)
(** ** Programm equivalence with worker/wrappers
Finally we show how the eliminator can be used to prove
program equivalences involving a worker/wrapper definition.
Here [indexes l] computes the list [0..|l|-1] of valid indexes in the
list [l].
*)
Equations indexes : list nat -> list nat :=
indexes l := go [] (length l)
where go : list nat -> nat -> list nat :=
go acc 0 := acc;
go acc (S n) := go (n :: acc) n.
(** Clearly, all indexes in the resulting list should be smaller than [length l]: *)
Lemma indexes_spec (l : list nat) : Forall (fun x => x < length l) (indexes l).
Proof.
(** We apply the eliminator, giving a predicate that specifies
preservation of the property from the accumulator to the end
result for [go]'s specification. The rest of the proof uses simple reasoning. *)
apply (indexes_elim (fun l indexesl => Forall (fun x => x < length l) indexesl)
(fun l acc n indexesl => n <= length l ->
Forall (fun x => x < length l) acc ->
Forall (fun x => x < length l) indexesl));
clear l; intros.
+ apply H; constructor.
+ apply H0.
+ apply H. lia. constructor. lia. apply H1.
Qed.
(** Using well-founded recursion we can also define an [interval x y]
function producing the interval [x..y-1] *)
Equations? interval x y : list nat by wf (y - x) lt :=
interval x y with lt_dec x y :=
{ | left ltxy => x :: interval (S x) y;
| right nltxy => [] }.
Proof. lia. Defined.
(** We prove a simple lemmas on [interval]: *)
Lemma interval_large x y : ~ x < y -> interval x y = [].
Proof. funelim (interval x y); clear Heq; intros; now try lia. Qed.
(** One can show that [indexes l] produces the interval [0..|l|-1] using [indexes_elim].
The recursion invariant for [indexes_go] records that [acc] corresponds to a partial
interval [n..|l|-1] during the computation, and is finally completed into [0..|l|-1]
by the end of the computation. We use the previous lemmas as helpers.
*)
Lemma indexes_interval l : indexes l = interval 0 (length l).
Proof.
set (P := fun start (l indexesl : list nat) => indexesl = interval start (length l)).
revert l.
apply (indexes_elim (P 0)
(fun l acc n indexesl =>
n <= length l ->
P n l acc -> P 0 l indexesl)); subst P; simpl.
intros l.
+ intros H. apply H; auto. rewrite interval_large; trivial; lia.
+ intros; trivial.
+ intros l ? n H Hn ->. apply H. lia.
rewrite (interval_equation_1 n).
destruct lt_dec. reflexivity. elim n0. lia.
Qed.