-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathlecture06.v
514 lines (405 loc) · 12.6 KB
/
lecture06.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
From mathcomp Require Import ssreflect ssrfun ssrbool eqtype ssrnat.
Set Implicit Arguments.
Unset Strict Implicit.
Unset Printing Implicit Defensive.
(*|
====================================================
Proofs by induction. The SSReflect proof methodology
====================================================
:Author: Anton Trunov
:Date: April 15, 2021
====================================================
|*)
(*|
Proofs by induction
------------------- |*)
(*|
Nested induction
================ |*)
(*| Let's prove addition is commutative |*)
Lemma addnC :
commutative addn.
Proof.
move=> x y.
elim: x.
(*| The goal at this point is `y = y + 0` (if we take
reduction into account), but to prove it we
need induction again! |*)
- rewrite add0n.
elim: y=> // y IHy.
rewrite addSn -IHy.
done.
(*| But, of course, it's better to factor this
proof out into a separate lemma `addn0`. Sometimes
this nested induction is not really avoidable
and it might not make sense to make a new lemma,
then nested induction is something to consider.
|*)
Restart.
(*| Let us prove this lemma idiomatically |*)
elim=> [| x IHx] y; first by rewrite addn0.
by rewrite addSn IHx -addSnnS.
Qed.
(*| The `first` tactical in the proof above lets
us not focus on trivial goals and break our proof
flow and apply `rewrite addn0` only to the *first*
subgoal generated by the `elim` tactic. |*)
(*|
Generalizing Induction Hypothesis
================================= |*)
(*| Let turn out attention to the proverbial
factorial function. Its standard implementation is
non-tail-recursive which is not a problem for us,
of course, given that the call stack is not going
to grow large. Still, let's see a common pattern
arising in this context. Mathcomp defines a
postfix notation to mean `factorial`: |*)
Locate "`!".
Print factorial.
Print fact_rec.
(*| Let's define our own tail-recursive version of
the factorial function. |*)
Fixpoint factorial_helper (n : nat) (acc : nat) : nat :=
if n is n'.+1 then
factorial_helper n' (n * acc)
else
acc.
(** The iterative implementation of the factorial
function: *)
Definition factorial_iter (n : nat) : nat :=
factorial_helper n 1.
(** Let's prove our iterative implementation of
factorial is correct. *)
Lemma factorial_iter_correct n :
factorial_iter n = n`!.
Proof.
elim: n.
- done.
move=> n IHn.
(*| To proceed let's simplify the goal |*)
rewrite /factorial_iter.
move=> /=.
rewrite muln1.
(*| At this point it should be clear that the
induction hypothesis is not directly applicable in
the goal and we should unfold `factorial_iter` in
it too. |*)
rewrite /factorial_iter in IHn.
(*| And now we are stuck here: our induction
hypothesis is not general enough to help us
because it has the second argument to
`factorial_helper` fixed to `1` but we need it to
work for `n.+1` too.
At this point we abort the proof and generalize
our lemma statement. This is a common pattern in
proofs by induction. |*)
Abort.
(*| We need to state our lemma in a way which does
not fix the second argument, i.e. we replace it
with a variable `acc` and formulate the
specification of `factorial_helper_correct` in
terms of its both arguments. A little thinking
reveals that statement: we start with `acc` and
mutliply it repeatedly by `n`, `n-1`, etc. |*)
Lemma factorial_helper_correct n acc :
factorial_helper n acc = n`! * acc.
Proof.
elim: n=> [|n IHn /=]; first by rewrite fact0 mul1n.
(*| We seem to be stuck again because `acc` in the
induction hypothesis does not match `(n.+1 * acc)`
in the goal. This happens because we again fix the
accumulator too early and make our induction
hypothesis too specialized. Let's start over and
generalize our induction hypothesis. |*)
Restart.
(*| We now move `acc` from the proof context to the goal, thus generalizing it. |*)
move: acc.
elim: n.
- move=> acc.
by rewrite fact0 mul1n.
move=> n IHn acc /=.
(*| Now our induction hypothesis is universally
quantified over `acc` and hence can be specialized
to any value of the `acc` parameter. The `rewrite`
tactic can take care of this specialization to
`n.+1 * acc`. |*)
rewrite IHn.
by rewrite factS mulnCA mulnA.
Restart.
(*| After a little refactoring we get the
following proof. Notice that we can combine steps
like `move: acc` followed by `elim: n` into one
step: `elim: n acc` which is a clear indicator
that your induction hypothesis needs
generalization for the proof to go through. It
would *not* be idiomatic to generalize induction
hypotheses unnecessarily. |*)
elim: n acc=> [|n IHn /=] acc; first by rewrite fact0 mul1n.
by rewrite IHn factS mulnCA mulnA.
Qed.
(*| And now we are able to prove our main
correctness lemma: |*)
Lemma factorial_iter_correct n :
factorial_iter n = n`!.
Proof.
rewrite /factorial_iter.
by rewrite factorial_helper_correct muln1.
Qed.
(*|
On searching for lemmas to use
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |*)
(*| Using the `Search` command can be tricky
sometimes. For example, `Search (1 * _)` won't
find what is needed to simplify `1 * n` into `n`
(and these are not definitionally equal).
The search results are unhelpful because this sort
of lemma is formulated using the `left_id`
defintion. Since looking for `left_id` would
return a bit too many lemmas, let's narrow the
search space down by telling the `Search` command
to restrict the search result to containing _both_
[left_id] and [muln] definitions: |*)
Search _ left_id muln.
(*| To learn more about this and related issues
checkout this wiki page:
https://github.com/math-comp/math-comp/wiki/Search. |*)
(*|
Custom Induction Principles
=========================== |*)
(*| Let's define a recursive Fibonacci function
for the illustration purposes (yes, it's factorial
*and* Fibonacci in one lecture, a combo!). |*)
(*| Our first attempt at defining the Fibonacci function
is going to fail, perhaps surprisingly. |*)
Fail Fixpoint fib (n : nat) : nat :=
if n is n''.+2 then
fib n'' + fib n''.+1
else n.
(*| Coq cannot figure out we are using structural
recursion here, because it does not see `n''.+1`
is a subterm of `n` but it simply needs a hint.
|*)
(*| Here is the hint: name a structural subterm
explicitly using the `as`-annotation |*)
Fixpoint fib (n : nat) : nat :=
if n is (n''.+1 as n').+1 then
fib n'' + fib n'
else n.
(*| But before we proceed we are going to need to
change the reduction behavior of `fib`. Let's
illustrate the issue by the means of an example.
|*)
Section Illustrate_simpl_nomatch.
Variable n : nat.
Lemma default_behavior :
fib n.+2 = 0.
Proof.
move=> /=.
(*| When doing proofs one usually does not want
reduction to make the goals harder to read and
understand and exposing a `match`-expression like
this certainly makes it harder to read and
understand. So `fib n.+1` should not get
simplified. |*)
Abort.
(*| Let's forbid simplification of `fib` if it
ends up like that, exposing the underlying
`match`-expression. |*)
Arguments fib n : simpl nomatch.
Lemma after_simpl_nomatch :
fib n.+2 = 0.
Proof.
move=> /=.
(*| This goal is what we want! |*)
Abort.
End Illustrate_simpl_nomatch.
(*| The results of the `Arguments` command does
not survive sections so we have to repeat it here.
(Actually there is a few things that don't survive
sections, notations most notable). |*)
Arguments fib n : simpl nomatch.
(*| For the sake of demonstration of certain proof
techniques let us define an iterative version of
the Fibonacci function. |*)
Fixpoint fib_iter (n : nat) (f0 f1 : nat) : nat :=
if n is n'.+1 then
fib_iter n' f1 (f0 + f1)
else f0.
Arguments fib_iter : simpl nomatch.
(*| Sometimes one just needs a one-step
simplification lemma, so it can be done manually
in our case. Although, the Equations plugin
provides this functionality out-of-box. |*)
Lemma fib_iterS n f0 f1 :
fib_iter n.+1 f0 f1 = fib_iter n f1 (f0 + f1).
Proof. by []. Qed.
(*| We are going to need the following helper
lemma which says `fib_iter` behave like the
Fibonacci function. |*)
Lemma fib_iter_sum n f0 f1 :
fib_iter n.+2 f0 f1 =
fib_iter n f0 f1 + fib_iter n.+1 f0 f1.
Proof.
(*| Notice we generalize the induction hypothesis here. |*)
elim: n f0 f1 => [//|n IHn] f0 f1.
rewrite fib_iterS.
rewrite IHn.
done.
Qed.
(*| Now we can try proving the main correctness lemmas. |*)
Lemma fib_iter_correct n :
fib_iter n 0 1 = fib n.
Proof.
elim: n=> //= n IHn.
(*| We have used `//=` switch here -- it combines
`//` and `/=` into one |*)
(*| And we are stuck again. The induction
hypothesis is not general enough.
Informally, regular induction works using the
induction step from the previous value of `n` to
go to the current and it work fine for functions
with a simple recursion pattern, but `fib` uses
*two* previous values of `n` to compute the next
Fibonacci number and this calls for a more
powerful induction pattern. |*)
Abort.
(*|
Pair induction
^^^^^^^^^^^^^^ |*)
(*| To make the proof go through we can design a
*custom induction principle*. This induction
principle repeats, in a sense, the structure of
the (recursive) Fibonacci function. |*)
(*|
.. code-block:: Coq
Lemma nat_ind2 (P : nat -> Prop) :
P 0 ->
P 1 ->
(forall n, P n -> P n.+1 -> P n.+2) ->
forall n, P n.
|*)
(*| Compare this to the regular induction principle:
.. code-block:: Coq
Lemma nat_ind (P : nat -> Prop) :
P 0 ->
(forall n, P n -> P n.+1) ->
forall n, P n.
|*)
(*| Now, let us prove `nat_ind2`: |*)
Lemma nat_ind2 (P : nat -> Prop) :
P 0 ->
P 1 ->
(forall n, P n -> P n.+1 -> P n.+2) ->
forall n, P n.
Proof.
move=> p0 p1 Istep n.
(*| If we did regular induction we would get
ourselves into the same trap as with
`fib_iter_correct` lemma. So let's prove a
_stronger_ goal instead. Why stronger goal?
Because a stronger goal results in a stronger
induction hypothesis!
Let's use the `have` tactic to do that. This
tactic lets us to *forward* reasoning as opposed
to backward reasoning we use with the `apply`
tactic. |*)
have: P n /\ P n.+1.
(*| `have` generates two subgoals:
1. It makes us prove the new statement we specified.
2. It makes us prove the new statement implies our old goal.
|*)
- elim: n=> // n [IHn IHSn].
split=> //.
by apply: Istep.
by case.
(*| Let's refactor the proof into a more idiomatic one
using the `suff` (`suffices`) tactic which is like `have`
but swaps the two subgoals. |*)
Restart.
move=> p0 p1 Istep n; suff: P n /\ P n.+1 by case.
elim: n=> // n [IHn IHSn]; split=> //; exact: Istep.
(*| An even shorter version would look something
like so. |*)
Restart.
move=> p0 p1 Istep n; suff: P n /\ P n.+1 by case.
by elim: n=> // n [/Istep pn12] /[dup]/pn12.
(*| Let us replay it one more time with some
manual breaks in between. |*)
Restart.
move=> p0 p1 Istep n; suffices: P n /\ P n.+1 by case.
elim: n=> // n.
move=> [/Istep pn12].
move=> /[dup].
move=> /pn12.
done.
Qed.
(*| We have used the `/[dup]` action to duplicate
the assumption at the top of the goal stack. |*)
(*| Now we can apply the custom induction
principle we just proved using
`elim/ind_principle` version of the `elim` tactic.
Notice that the slash symbol here does *not* mean
"view" as in `move /View`. *)
Lemma fib_iter_correct n :
fib_iter n 0 1 = fib n.
Proof.
elim/nat_ind2: n => // n IHn1 IHn2.
by rewrite fib_iter_sum IHn1 IHn2.
Qed.
(*| Note: `fib_iter_correct` can be proven using
the `suffices` tactic too:
`fib_iter n 0 1 = fib n /\ fib_iter n.+1 0 1 = fib n.+1`.
|*)
(*|
Complete induction
^^^^^^^^^^^^^^^^^^
|*)
(*|
Now we are going to cover an even more
general induction principle called *complete induction*.x
It's also called:
- strong induction;
- well-founded induction;
- course-of-values induction.
The statement is called `ltn_ind` and looks like so:
.. code-block:: Coq
(forall m, (forall k : nat, (k < m) -> P k) -> P m) ->
forall n, P n.
This means that to prove property `P` holds for an arbitrary
natural number `n`, one can assume `P` holds for any `k`
smaller than `n`. *)
(*| Let's use this induction principle to prove
`fib_iter` correct one more time |*)
Lemma fib_iter_correct' n :
fib_iter n 0 1 = fib n.
Proof.
elim/ltn_ind: n=> n IHn.
Fail case: n.
case: n IHn=> // n IHn.
case: n IHn=> // n IHn.
rewrite fib_iter_sum.
rewrite !IHn.
- done.
- done.
done.
Restart.
(*| A more idiomatic proof would look something
like this. |*)
elim/ltn_ind: n=> [] // [] // [] // n IHn.
by rewrite fib_iter_sum !IHn.
Qed.
(*|
Summary
------- |*)
(*|
Tactic/tactical summary
======================= |*)
(*|
- `/=`: simplification action;
- `//=`: solve trivial subgoals and simplify;
- `elim/custom_induction_principle`:
we specify the induction principle to use;
- `have`, `suff` (`suffices`): forward reasoning;
- `rewrite !E`: rewrite 1 or more times with the
equation `E` until no more rewrites are possible.
|*)