forked from fabriceleal/Software-Foundations-Solutions
-
Notifications
You must be signed in to change notification settings - Fork 0
/
UseAuto.v
1936 lines (1598 loc) · 71.8 KB
/
UseAuto.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
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(** * UseAuto: Theory and Practice of Automation in Coq Proofs *)
(* Chapter maintained by Arthur Chargueraud *)
(** In a machine-checked proof, every single detail has to be
justified. This can result in huge proof scripts. Fortunately,
Coq comes with a proof-search mechanism and with several decision
procedures that enable the system to automatically synthesize
simple pieces of proof. Automation is very powerful when set up
appropriately. The purpose of this chapter is to explain the
basics of working of automation.
The chapter is organized in two parts. The first part focuses on a
general mechanism called "proof search." In short, proof search
consists in naively trying to apply lemmas and assumptions in all
possible ways. The second part describes "decision procedures",
which are tactics that are very good at solving proof obligations
that fall in some particular fragment of the logic of Coq.
Many of the examples used in this chapter consist of small lemmas
that have been made up to illustrate particular aspects of automation.
These examples are completely independent from the rest of the Software
Foundations course. This chapter also contains some bigger examples
which are used to explain how to use automation in realistic proofs.
These examples are taken from other chapters of the course (mostly
from STLC), and the proofs that we present make use of the tactics
from the library [LibTactics.v], which is presented in the chapter
[UseTactics]. *)
Require Import LibTactics.
(* ####################################################### *)
(** * Basic Features of Proof Search *)
(** The idea of proof search is to replace a sequence of tactics
applying lemmas and assumptions with a call to a single tactic,
for example [auto]. This form of proof automation saves a lot of
effort. It typically leads to much shorter proof scripts, and to
scripts that are typically more robust to change. If one makes a
little change to a definition, a proof that exploits automation
probably won't need to be modified at all. Of course, using too
much automation is a bad idea. When a proof script no longer
records the main arguments of a proof, it becomes difficult to fix
it when it gets broken after a change in a definition. Overall, a
reasonable use of automation is generally a big win, as it saves a
lot of time both in building proof scripts and in subsequently
maintaining those proof scripts. *)
(* ####################################################### *)
(** ** Strength of Proof Search *)
(** We are going to study four proof-search tactics: [auto], [eauto],
[iauto] and [jauto]. The tactics [auto] and [eauto] are builtin
in Coq. The tactic [iauto] is a shorthand for the builtin tactic
[try solve [intuition eauto]]. The tactic [jauto] is defined in
the library [LibTactics], and simply performs some preprocessing
of the goal before calling [eauto]. The goal of this chapter is
to explain the general principles of proof search and to give
rule of thumbs for guessing which of the four tactics mentioned
above is best suited for solving a given goal.
Proof search is a compromise between efficiency and
expressiveness, that is, a tradeoff between how complex goals the
tactic can solve and how much time the tactic requires for
terminating. The tactic [auto] builds proofs only by using the
basic tactics [reflexivity], [assumption], and [apply]. The tactic
[eauto] can also exploit [eapply]. The tactic [jauto] extends
[eauto] by being able to open conjunctions and existentials that
occur in the context. The tactic [iauto] is able to deal with
conjunctions, disjunctions, and negation in a quite clever way;
however it is not able to open existentials from the context.
Also, [iauto] usually becomes very slow when the goal involves
several disjunctions.
Note that proof search tactics never perform any rewriting
step (tactics [rewrite], [subst]), nor any case analysis on an
arbitrary data structure or predicate (tactics [destruct] and
[inversion]), nor any proof by induction (tactic [induction]). So,
proof search is really intended to automate the final steps from
the various branches of a proof. It is not able to discover the
overall structure of a proof. *)
(* ####################################################### *)
(** ** Basics *)
(** The tactic [auto] is able to solve a goal that can be proved
using a sequence of [intros], [apply], [assumption], and [reflexivity].
Two examples follow. The first one shows the ability for
[auto] to call [reflexivity] at any time. In fact, calling
[reflexivity] is always the first thing that [auto] tries to do. *)
Lemma solving_by_reflexivity :
2 + 3 = 5.
Proof. auto. Qed.
(** The second example illustrates a proof where a sequence of
two calls to [apply] are needed. The goal is to prove that
if [Q n] implies [P n] for any [n] and if [Q n] holds for any [n],
then [P 2] holds. *)
Lemma solving_by_apply : forall (P Q : nat->Prop),
(forall n, Q n -> P n) ->
(forall n, Q n) ->
P 2.
Proof. auto. Qed.
(** We can ask [auto] to tell us what proof it came up with,
by invoking [info_auto] in place of [auto]. *)
Lemma solving_by_apply' : forall (P Q : nat->Prop),
(forall n, Q n -> P n) ->
(forall n, Q n) ->
P 2.
Proof. info_auto. Qed.
(* The output is: [intro P; intro Q; intro H;] *)
(* followed with [intro H0; simple apply H; simple apply H0]. *)
(* i.e., the sequence [intros P Q H H0; apply H; apply H0]. *)
(** The tactic [auto] can invoke [apply] but not [eapply]. So, [auto]
cannot exploit lemmas whose instantiation cannot be directly
deduced from the proof goal. To exploit such lemmas, one needs to
invoke the tactic [eauto], which is able to call [eapply].
In the following example, the first hypothesis asserts that [P n]
is true when [Q m] is true for some [m], and the goal is to prove
that [Q 1] implies [P 2]. This implication follows direction from
the hypothesis by instantiating [m] as the value [1]. The
following proof script shows that [eauto] successfully solves the
goal, whereas [auto] is not able to do so. *)
Lemma solving_by_eapply : forall (P Q : nat->Prop),
(forall n m, Q m -> P n) ->
Q 1 -> P 2.
Proof. auto. eauto. Qed.
(** Remark: Again, we can use [info_eauto] to see what proof [eauto]
comes up with. *)
(* ####################################################### *)
(** ** Conjunctions *)
(** So far, we've seen that [eauto] is stronger than [auto] in the
sense that it can deal with [eapply]. In the same way, we are going
to see how [jauto] and [iauto] are stronger than [auto] and [eauto]
in the sense that they provide better support for conjunctions. *)
(** The tactics [auto] and [eauto] can prove a goal of the form
[F /\ F'], where [F] and [F'] are two propositions, as soon as
both [F] and [F'] can be proved in the current context.
An example follows. *)
Lemma solving_conj_goal : forall (P : nat->Prop) (F : Prop),
(forall n, P n) -> F -> F /\ P 2.
Proof. auto. Qed.
(** However, when an assumption is a conjunction, [auto] and [eauto]
are not able to exploit this conjunction. It can be quite
surprising at first that [eauto] can prove very complex goals but
that it fails to prove that [F /\ F'] implies [F]. The tactics
[iauto] and [jauto] are able to decompose conjunctions from the context.
Here is an example. *)
Lemma solving_conj_hyp : forall (F F' : Prop),
F /\ F' -> F.
Proof. auto. eauto. jauto. (* or [iauto] *) Qed.
(** The tactic [jauto] is implemented by first calling a
pre-processing tactic called [jauto_set], and then calling
[eauto]. So, to understand how [jauto] works, one can directly
call the tactic [jauto_set]. *)
Lemma solving_conj_hyp' : forall (F F' : Prop),
F /\ F' -> F.
Proof. intros. jauto_set. eauto. Qed.
(** Next is a more involved goal that can be solved by [iauto] and
[jauto]. *)
Lemma solving_conj_more : forall (P Q R : nat->Prop) (F : Prop),
(F /\ (forall n m, (Q m /\ R n) -> P n)) ->
(F -> R 2) ->
Q 1 ->
P 2 /\ F.
Proof. jauto. (* or [iauto] *) Qed.
(** The strategy of [iauto] and [jauto] is to run a global analysis of
the top-level conjunctions, and then call [eauto]. For this
reason, those tactics are not good at dealing with conjunctions
that occur as the conclusion of some universally quantified
hypothesis. The following example illustrates a general weakness
of Coq proof search mechanisms. *)
Lemma solving_conj_hyp_forall : forall (P Q : nat->Prop),
(forall n, P n /\ Q n) -> P 2.
Proof.
auto. eauto. iauto. jauto.
(* Nothing works, so we have to do some of the work by hand *)
intros. destruct (H 2). auto.
Qed.
(** This situation is slightly disappointing, since automation is
able to prove the following goal, which is very similar. The
only difference is that the universal quantification has been
distributed over the conjunction. *)
Lemma solved_by_jauto : forall (P Q : nat->Prop) (F : Prop),
(forall n, P n) /\ (forall n, Q n) -> P 2.
Proof. jauto. (* or [iauto] *) Qed.
(* ####################################################### *)
(** ** Disjunctions *)
(** The tactics [auto] and [eauto] can handle disjunctions that
occur in the goal. *)
Lemma solving_disj_goal : forall (F F' : Prop),
F -> F \/ F'.
Proof. auto. Qed.
(** However, only [iauto] is able to automate reasoning on the
disjunctions that appear in the context. For example, [iauto] can
prove that [F \/ F'] entails [F' \/ F]. *)
Lemma solving_disj_hyp : forall (F F' : Prop),
F \/ F' -> F' \/ F.
Proof. auto. eauto. jauto. iauto. Qed.
(** More generally, [iauto] can deal with complex combinations of
conjunctions, disjunctions, and negations. Here is an example. *)
Lemma solving_tauto : forall (F1 F2 F3 : Prop),
((~F1 /\ F3) \/ (F2 /\ ~F3)) ->
(F2 -> F1) ->
(F2 -> F3) ->
~F2.
Proof. iauto. Qed.
(** However, the ability of [iauto] to automatically perform a case
analysis on disjunctions comes with a downside: [iauto] may be
very slow. If the context involves several hypotheses with
disjunctions, [iauto] typically generates an exponential number of
subgoals on which [eauto] is called. One major advantage of [jauto]
compared with [iauto] is that it never spends time performing this
kind of case analyses. *)
(* ####################################################### *)
(** ** Existentials *)
(** The tactics [eauto], [iauto], and [jauto] can prove goals whose
conclusion is an existential. For example, if the goal is [exists
x, f x], the tactic [eauto] introduces an existential variable,
say [?25], in place of [x]. The remaining goal is [f ?25], and
[eauto] tries to solve this goal, allowing itself to instantiate
[?25] with any appropriate value. For example, if an assumption [f
2] is available, then the variable [?25] gets instantiated with
[2] and the goal is solved, as shown below. *)
Lemma solving_exists_goal : forall (f : nat->Prop),
f 2 -> exists x, f x.
Proof.
auto. (* observe that [auto] does not deal with existentials, *)
eauto. (* whereas [eauto], [iauto] and [jauto] solve the goal *)
Qed.
(** A major strength of [jauto] over the other proof search tactics is
that it is able to exploit the existentially-quantified
hypotheses, i.e., those of the form [exists x, P]. *)
Lemma solving_exists_hyp : forall (f g : nat->Prop),
(forall x, f x -> g x) ->
(exists a, f a) ->
(exists a, g a).
Proof.
auto. eauto. iauto. (* All of these tactics fail, *)
jauto. (* whereas [jauto] succeeds. *)
(* For the details, run [intros. jauto_set. eauto] *)
Qed.
(* ####################################################### *)
(** ** Negation *)
(** The tactics [auto] and [eauto] suffer from some limitations with
respect to the manipulation of negations, mostly related to the
fact that negation, written [~ P], is defined as [P -> False] but
that the unfolding of this definition is not performed
automatically. Consider the following example. *)
Lemma negation_study_1 : forall (P : nat->Prop),
P 0 -> (forall x, ~ P x) -> False.
Proof.
intros P H0 HX.
eauto. (* It fails to see that [HX] applies *)
unfold not in *. eauto.
Qed.
(** For this reason, the tactics [iauto] and [jauto] systematically
invoke [unfold not in *] as part of their pre-processing. So,
they are able to solve the previous goal right away. *)
Lemma negation_study_2 : forall (P : nat->Prop),
P 0 -> (forall x, ~ P x) -> False.
Proof. jauto. (* or [iauto] *) Qed.
(** We will come back later on to the behavior of proof search with
respect to the unfolding of definitions. *)
(* ####################################################### *)
(** ** Equalities *)
(** Coq's proof-search feature is not good at exploiting equalities.
It can do very basic operations, like exploiting reflexivity
and symmetry, but that's about it. Here is a simple example
that [auto] can solve, by first calling [symmetry] and then
applying the hypothesis. *)
Lemma equality_by_auto : forall (f g : nat->Prop),
(forall x, f x = g x) -> g 2 = f 2.
Proof. auto. Qed.
(** To automate more advanced reasoning on equalities, one should
rather try to use the tactic [congruence], which is presented at
the end of this chapter in the "Decision Procedures" section. *)
(* ####################################################### *)
(** * How Proof Search Works *)
(* ####################################################### *)
(** ** Search Depth *)
(** The tactic [auto] works as follows. It first tries to call
[reflexivity] and [assumption]. If one of these calls solves the
goal, the job is done. Otherwise [auto] tries to apply the most
recently introduced assumption that can be applied to the goal
without producing and error. This application produces
subgoals. There are two possible cases. If the sugboals produced
can be solved by a recursive call to [auto], then the job is done.
Otherwise, if this application produces at least one subgoal that
[auto] cannot solve, then [auto] starts over by trying to apply
the second most recently introduced assumption. It continues in a
similar fashion until it finds a proof or until no assumption
remains to be tried.
It is very important to have a clear idea of the backtracking
process involved in the execution of the [auto] tactic; otherwise
its behavior can be quite puzzling. For example, [auto] is not
able to solve the following triviality. *)
Lemma search_depth_0 :
True /\ True /\ True /\ True /\ True /\ True.
Proof.
auto.
Abort.
(** The reason [auto] fails to solve the goal is because there are
too many conjunctions. If there had been only five of them, [auto]
would have successfully solved the proof, but six is too many.
The tactic [auto] limits the number of lemmas and hypotheses
that can be applied in a proof, so as to ensure that the proof
search eventually terminates. By default, the maximal number
of steps is five. One can specify a different bound, writing
for example [auto 6] to search for a proof involving at most
six steps. For example, [auto 6] would solve the previous lemma.
(Similarly, one can invoke [eauto 6] or [intuition eauto 6].)
The argument [n] of [auto n] is called the "search depth."
The tactic [auto] is simply defined as a shorthand for [auto 5].
The behavior of [auto n] can be summarized as follows. It first
tries to solve the goal using [reflexivity] and [assumption]. If
this fails, it tries to apply a hypothesis (or a lemma that has
been registered in the hint database), and this application
produces a number of sugoals. The tactic [auto (n-1)] is then
called on each of those subgoals. If all the subgoals are solved,
the job is completed, otherwise [auto n] tries to apply a
different hypothesis.
During the process, [auto n] calls [auto (n-1)], which in turn
might call [auto (n-2)], and so on. The tactic [auto 0] only
tries [reflexivity] and [assumption], and does not try to apply
any lemma. Overall, this means that when the maximal number of
steps allowed has been exceeded, the [auto] tactic stops searching
and backtracks to try and investigate other paths. *)
(** The following lemma admits a unique proof that involves exactly
three steps. So, [auto n] proves this goal iff [n] is greater than
three. *)
Lemma search_depth_1 : forall (P : nat->Prop),
P 0 ->
(P 0 -> P 1) ->
(P 1 -> P 2) ->
(P 2).
Proof.
auto 0. (* does not find the proof *)
auto 1. (* does not find the proof *)
auto 2. (* does not find the proof *)
auto 3. (* finds the proof *)
(* more generally, [auto n] solves the goal if [n >= 3] *)
Qed.
(** We can generalize the example by introducing an assumption
asserting that [P k] is derivable from [P (k-1)] for all [k],
and keep the assumption [P 0]. The tactic [auto], which is the
same as [auto 5], is able to derive [P k] for all values of [k]
less than 5. For example, it can prove [P 4]. *)
Lemma search_depth_3 : forall (P : nat->Prop),
(* Hypothesis H1: *) (P 0) ->
(* Hypothesis H2: *) (forall k, P (k-1) -> P k) ->
(* Goal: *) (P 4).
Proof. auto. Qed.
(** However, to prove [P 5], one needs to call at least [auto 6]. *)
Lemma search_depth_4 : forall (P : nat->Prop),
(* Hypothesis H1: *) (P 0) ->
(* Hypothesis H2: *) (forall k, P (k-1) -> P k) ->
(* Goal: *) (P 5).
Proof. auto. auto 6. Qed.
(** Because [auto] looks for proofs at a limited depth, there are
cases where [auto] can prove a goal [F] and can prove a goal
[F'] but cannot prove [F /\ F']. In the following example,
[auto] can prove [P 4] but it is not able to prove [P 4 /\ P 4],
because the splitting of the conjunction consumes one proof step.
To prove the conjunction, one needs to increase the search depth,
using at least [auto 6]. *)
Lemma search_depth_5 : forall (P : nat->Prop),
(* Hypothesis H1: *) (P 0) ->
(* Hypothesis H2: *) (forall k, P (k-1) -> P k) ->
(* Goal: *) (P 4 /\ P 4).
Proof. auto. auto 6. Qed.
(* ####################################################### *)
(** ** Backtracking *)
(** In the previous section, we have considered proofs where
at each step there was a unique assumption that [auto]
could apply. In general, [auto] can have several choices
at every step. The strategy of [auto] consists of trying all
of the possibilities (using a depth-first search exploration).
To illustrate how automation works, we are going to extend the
previous example with an additional assumption asserting that
[P k] is also derivable from [P (k+1)]. Adding this hypothesis
offers a new possibility that [auto] could consider at every step.
There exists a special command that one can use for tracing
all the steps that proof-search considers. To view such a
trace, one should write [debug eauto]. (For some reason, the
command [debug auto] does not exist, so we have to use the
command [debug eauto] instead.) *)
Lemma working_of_auto_1 : forall (P : nat->Prop),
(* Hypothesis H1: *) (P 0) ->
(* Hypothesis H2: *) (forall k, P (k+1) -> P k) ->
(* Hypothesis H3: *) (forall k, P (k-1) -> P k) ->
(* Goal: *) (P 2).
(* Uncomment "debug" in the following line to see the debug trace: *)
Proof. intros P H1 H2 H3. debug eauto. Qed.
(** The output message produced by [debug eauto] is as follows.
<<
depth=5
depth=4 apply H3
depth=3 apply H3
depth=3 exact H1
>>
The depth indicates the value of [n] with which [eauto n] is
called. The tactics shown in the message indicate that the first
thing that [eauto] has tried to do is to apply [H3]. The effect of
applying [H3] is to replace the goal [P 2] with the goal [P 1].
Then, again, [H3] has been applied, changing the goal [P 1] into
[P 0]. At that point, the goal was exactly the hypothesis [H1].
It seems that [eauto] was quite lucky there, as it never even
tried to use the hypothesis [H2] at any time. The reason is that
[auto] always tries to use the most recently introduced hypothesis
first, and [H3] is a more recent hypothesis than [H2] in the goal.
So, let's permute the hypotheses [H2] and [H3] and see what
happens. *)
Lemma working_of_auto_2 : forall (P : nat->Prop),
(* Hypothesis H1: *) (P 0) ->
(* Hypothesis H3: *) (forall k, P (k-1) -> P k) ->
(* Hypothesis H2: *) (forall k, P (k+1) -> P k) ->
(* Goal: *) (P 2).
Proof. intros P H1 H3 H2. debug eauto. Qed.
(** This time, the output message suggests that the proof search
investigates many possibilities. Replacing [debug eauto] with
[info_eauto], we observe that the proof that [eauto] comes up
with is actually not the simplest one.
[apply H2; apply H3; apply H3; apply H3; exact H1]
This proof goes through the proof obligation [P 3], even though
it is not any useful. The following tree drawing describes
all the goals that automation has been through.
<<
|5||4||3||2||1||0| -- below, tabulation indicates the depth
[P 2]
-> [P 3]
-> [P 4]
-> [P 5]
-> [P 6]
-> [P 7]
-> [P 5]
-> [P 4]
-> [P 5]
-> [P 3]
--> [P 3]
-> [P 4]
-> [P 5]
-> [P 3]
-> [P 2]
-> [P 3]
-> [P 1]
-> [P 2]
-> [P 3]
-> [P 4]
-> [P 5]
-> [P 3]
-> [P 2]
-> [P 3]
-> [P 1]
-> [P 1]
-> [P 2]
-> [P 3]
-> [P 1]
-> [P 0]
-> !! Done !!
>>
The first few lines read as follows. To prove [P 2], [eauto 5]
has first tried to apply [H2], producing the subgoal [P 3].
To solve it, [eauto 4] has tried again to apply [H2], producing
the goal [P 4]. Similarly, the search goes through [P 5], [P 6]
and [P 7]. When reaching [P 7], the tactic [eauto 0] is called
but as it is not allowed to try and apply any lemma, it fails.
So, we come back to the goal [P 6], and try this time to apply
hypothesis [H3], producing the subgoal [P 5]. Here again,
[eauto 0] fails to solve this goal.
The process goes on and on, until backtracking to [P 3] and trying
to apply [H2] three times in a row, going through [P 2] and [P 1]
and [P 0]. This search tree explains why [eauto] came up with a
proof starting with [apply H2]. *)
(* ####################################################### *)
(** ** Adding Hints *)
(** By default, [auto] (and [eauto]) only tries to apply the
hypotheses that appear in the proof context. There are two
possibilities for telling [auto] to exploit a lemma that have
been proved previously: either adding the lemma as an assumption
just before calling [auto], or adding the lemma as a hint, so
that it can be used by every calls to [auto].
The first possibility is useful to have [auto] exploit a lemma
that only serves at this particular point. To add the lemma as
hypothesis, one can type [generalize mylemma; intros], or simply
[lets: mylemma] (the latter requires [LibTactics.v]).
The second possibility is useful for lemmas that need to be
exploited several times. The syntax for adding a lemma as a hint
is [Hint Resolve mylemma]. For example, the lemma asserting than
any number is less than or equal to itself, [forall x, x <= x],
called [Le.le_refl] in the Coq standard library, can be added as a
hint as follows. *)
Hint Resolve Le.le_refl.
(** A convenient shorthand for adding all the constructors of an
inductive datatype as hints is the command [Hint Constructors
mydatatype].
Warning: some lemmas, such as transitivity results, should
not be added as hints as they would very badly affect the
performance of proof search. The description of this problem
and the presentation of a general work-around for transitivity
lemmas appear further on. *)
(* ####################################################### *)
(** ** Integration of Automation in Tactics *)
(** The library "LibTactics" introduces a convenient feature for
invoking automation after calling a tactic. In short, it suffices
to add the symbol star ([*]) to the name of a tactic. For example,
[apply* H] is equivalent to [apply H; auto_star], where [auto_star]
is a tactic that can be defined as needed.
The definition of [auto_star], which determines the meaning of the
star symbol, can be modified whenever needed. Simply write:
Ltac auto_star ::= a_new_definition.
]]
Observe the use of [::=] instead of [:=], which indicates that the
tactic is being rebound to a new definition. So, the default
definition is as follows. *)
Ltac auto_star ::= try solve [ jauto ].
(** Nearly all standard Coq tactics and all the tactics from
"LibTactics" can be called with a star symbol. For example, one
can invoke [subst*], [destruct* H], [inverts* H], [lets* I: H x],
[specializes* H x], and so on... There are two notable exceptions.
The tactic [auto*] is just another name for the tactic
[auto_star]. And the tactic [apply* H] calls [eapply H] (or the
more powerful [applys H] if needed), and then calls [auto_star].
Note that there is no [eapply* H] tactic, use [apply* H]
instead. *)
(** In large developments, it can be convenient to use two degrees of
automation. Typically, one would use a fast tactic, like [auto],
and a slower but more powerful tactic, like [jauto]. To allow for
a smooth coexistence of the two form of automation, [LibTactics.v]
also defines a "tilde" version of tactics, like [apply~ H],
[destruct~ H], [subst~], [auto~] and so on. The meaning of the
tilde symbol is described by the [auto_tilde] tactic, whose
default implementation is [auto]. *)
Ltac auto_tilde ::= auto.
(** In the examples that follow, only [auto_star] is needed. *)
(** An alternative, possibly more efficient version of auto_star is the
following":
Ltac auto_star ::= try solve [ eassumption | auto | jauto ].
With the above definition, [auto_star] first tries to solve the
goal using the assumptions; if it fails, it tries using [auto],
and if this still fails, then it calls [jauto]. Even though
[jauto] is strictly stronger than [eassumption] and [auto], it
makes sense to call these tactics first, because, when the
succeed, they save a lot of time, and when they fail to prove
the goal, they fail very quickly.".
*)
(* ####################################################### *)
(** * Examples of Use of Automation *)
(** Let's see how to use proof search in practice on the main theorems
of the "Software Foundations" course, proving in particular
results such as determinism, preservation and progress. *)
(* ####################################################### *)
(** ** Determinism *)
Module DeterministicImp.
Require Import Imp.
(** Recall the original proof of the determinism lemma for the IMP
language, shown below. *)
Theorem ceval_deterministic: forall c st st1 st2,
c / st || st1 ->
c / st || st2 ->
st1 = st2.
Proof.
intros c st st1 st2 E1 E2.
generalize dependent st2.
(ceval_cases (induction E1) Case); intros st2 E2; inversion E2; subst.
Case "E_Skip". reflexivity.
Case "E_Ass". reflexivity.
Case "E_Seq".
assert (st' = st'0) as EQ1.
SCase "Proof of assertion". apply IHE1_1; assumption.
subst st'0.
apply IHE1_2. assumption.
Case "E_IfTrue".
SCase "b1 evaluates to true".
apply IHE1. assumption.
SCase "b1 evaluates to false (contradiction)".
rewrite H in H5. inversion H5.
Case "E_IfFalse".
SCase "b1 evaluates to true (contradiction)".
rewrite H in H5. inversion H5.
SCase "b1 evaluates to false".
apply IHE1. assumption.
Case "E_WhileEnd".
SCase "b1 evaluates to true".
reflexivity.
SCase "b1 evaluates to false (contradiction)".
rewrite H in H2. inversion H2.
Case "E_WhileLoop".
SCase "b1 evaluates to true (contradiction)".
rewrite H in H4. inversion H4.
SCase "b1 evaluates to false".
assert (st' = st'0) as EQ1.
SSCase "Proof of assertion". apply IHE1_1; assumption.
subst st'0.
apply IHE1_2. assumption.
Qed.
(** Exercise: rewrite this proof using [auto] whenever possible.
(The solution uses [auto] 9 times.) *)
Theorem ceval_deterministic': forall c st st1 st2,
c / st || st1 ->
c / st || st2 ->
st1 = st2.
Proof.
introv E1 E2. gen st2.
induction E1; intros; inverts E2; tryfalse; subst*.
apply IHE1_1 in H1; subst*.
apply IHE1_1 in H3; subst*.
Qed.
(** In fact, using automation is not just a matter of calling [auto]
in place of one or two other tactics. Using automation is about
rethinking the organization of sequences of tactics so as to
minimize the effort involved in writing and maintaining the proof.
This process is eased by the use of the tactics from
[LibTactics.v]. So, before trying to optimize the way automation
is used, let's first rewrite the proof of determinism:
- use [introv H] instead of [intros x H],
- use [gen x] instead of [generalize dependent x],
- use [inverts H] instead of [inversion H; subst],
- use [tryfalse] to handle contradictions, and get rid of
the cases where [beval st b1 = true] and [beval st b1 = false]
both appear in the context,
- stop using [ceval_cases] to label subcases. *)
Theorem ceval_deterministic'': forall c st st1 st2,
c / st || st1 ->
c / st || st2 ->
st1 = st2.
Proof.
introv E1 E2. gen st2.
induction E1; intros; inverts E2; tryfalse.
auto.
auto.
assert (st' = st'0). auto. subst. auto.
auto.
auto.
auto.
assert (st' = st'0). auto. subst. auto.
Qed.
(** To obtain a nice clean proof script, we have to remove the calls
[assert (st' = st'0)]. Such a tactic invokation is not nice
because it refers to some variables whose name has been
automatically generated. This kind of tactics tend to be very
brittle. The tactic [assert (st' = st'0)] is used to assert the
conclusion that we want to derive from the induction
hypothesis. So, rather than stating this conclusion explicitly, we
are going to ask Coq to instantiate the induction hypothesis,
using automation to figure out how to instantiate it. The tactic
[forwards], described in [LibTactics.v] precisely helps with
instantiating a fact. So, let's see how it works out on our
example. *)
Theorem ceval_deterministic''': forall c st st1 st2,
c / st || st1 ->
c / st || st2 ->
st1 = st2.
Proof.
(* Let's replay the proof up to the [assert] tactic. *)
introv E1 E2. gen st2.
induction E1; intros; inverts E2; tryfalse.
auto. auto.
(* We duplicate the goal for comparing different proofs. *)
dup 4.
(* The old proof: *)
assert (st' = st'0). apply IHE1_1. apply H1.
(* produces [H: st' = st'0]. *) skip.
(* The new proof, without automation: *)
forwards: IHE1_1. apply H1.
(* produces [H: st' = st'0]. *) skip.
(* The new proof, with automation: *)
forwards: IHE1_1. eauto.
(* produces [H: st' = st'0]. *) skip.
(* The new proof, with integrated automation: *)
forwards*: IHE1_1.
(* produces [H: st' = st'0]. *) skip.
Abort.
(** To polish the proof script, it remains to factorize the calls
to [auto], using the star symbol. The proof of determinism can then
be rewritten in only four lines, including no more than 10 tactics. *)
Theorem ceval_deterministic'''': forall c st st1 st2,
c / st || st1 ->
c / st || st2 ->
st1 = st2.
Proof.
introv E1 E2. gen st2.
induction E1; intros; inverts* E2; tryfalse.
forwards*: IHE1_1. subst*.
forwards*: IHE1_1. subst*.
Qed.
End DeterministicImp.
(* ####################################################### *)
(** ** Preservation for STLC *)
Module PreservationProgressStlc.
Require Import StlcProp.
Import STLC.
Import STLCProp.
(** Consider the proof of perservation of STLC, shown below.
This proof already uses [eauto] through the triple-dot
mechanism. *)
Theorem preservation : forall t t' T,
has_type empty t T ->
t ==> t' ->
has_type empty t' T.
Proof with eauto.
remember (@empty ty) as Gamma.
intros t t' T HT. generalize dependent t'.
(has_type_cases (induction HT) Case); intros t' HE; subst Gamma.
Case "T_Var".
inversion HE.
Case "T_Abs".
inversion HE.
Case "T_App".
inversion HE; subst...
(* (step_cases (inversion HE) SCase); subst...*)
(* The ST_App1 and ST_App2 cases are immediate by induction, and
auto takes care of them *)
SCase "ST_AppAbs".
apply substitution_preserves_typing with T11...
inversion HT1...
Case "T_True".
inversion HE.
Case "T_False".
inversion HE.
Case "T_If".
inversion HE; subst...
Qed.
(** Exercise: rewrite this proof using tactics from [LibTactics]
and calling automation using the star symbol rather than the
triple-dot notation. More precisely, make use of the tactics
[inverts*] and [applys*] to call [auto*] after a call to
[inverts] or to [applys]. The solution is three lines long.*)
Theorem preservation' : forall t t' T,
has_type empty t T ->
t ==> t' ->
has_type empty t' T.
Proof.
remember (@empty ty) as Gamma. introv E1 E2. gen t'.
induction E1; introv HE; inversion HE; subst*.
apply* substitution_preserves_typing. inversion* E1_1.
Qed.
(* ####################################################### *)
(** ** Progress for STLC *)
(** Consider the proof of the progress theorem. *)
Theorem progress : forall t T,
has_type empty t T ->
value t \/ exists t', t ==> t'.
Proof with eauto.
intros t T Ht.
remember (@empty ty) as Gamma.
(has_type_cases (induction Ht) Case); subst Gamma...
Case "T_Var".
inversion H.
Case "T_App".
right. destruct IHHt1...
SCase "t1 is a value".
destruct IHHt2...
SSCase "t2 is a value".
inversion H; subst; try solve by inversion.
exists ([x0:=t2]t)...
SSCase "t2 steps".
destruct H0 as [t2' Hstp]. exists (tapp t1 t2')...
SCase "t1 steps".
destruct H as [t1' Hstp]. exists (tapp t1' t2)...
Case "T_If".
right. destruct IHHt1...
destruct t1; try solve by inversion...
inversion H. exists (tif x0 t2 t3)...
Qed.
(** Exercise: optimize the above proof.
Hint: make use of [destruct*] and [inverts*].
The solution consists of 10 short lines. *)
Theorem progress' : forall t T,
has_type empty t T ->
value t \/ exists t', t ==> t'.
Proof.
introv Ht.
remember (@empty ty) as Gamma.
induction Ht; subst*.
inverts* H.
destruct* IHHt1.
destruct* IHHt2.
inverts* H; try solve by inversion.
destruct IHHt1; destruct* t1;
try solve by inversion.
Qed.
End PreservationProgressStlc.
(* ####################################################### *)
(** ** BigStep and SmallStep *)
Module Semantics.
Require Import Smallstep.
(** Consider the proof relating a small-step reduction judgment
to a big-step reduction judgment. *)
Theorem multistep__eval : forall t v,
normal_form_of t v -> exists n, v = C n /\ t || n.
Proof.
intros t v Hnorm.
unfold normal_form_of in Hnorm.
inversion Hnorm as [Hs Hnf]; clear Hnorm.
rewrite nf_same_as_value in Hnf. inversion Hnf. clear Hnf.
exists n. split. reflexivity.
multi_cases (induction Hs) Case; subst.
Case "multi_refl".
apply E_Const.
Case "multi_step".
eapply step__eval. eassumption. apply IHHs. reflexivity.
Qed.
(** Our goal is to optimize the above proof. It is generally
easier to isolate inductions into separate lemmas. So,
we are going to first prove an intermediate result
that consists of the judgment over which the induction
is being performed. *)
(** Exercise: prove the following result, using tactics
[introv], [induction] and [subst], and [apply*].
The solution is 3 lines long. *)
Print C.
Theorem multistep_eval_ind : forall t v,
t ==>* v -> forall n, C n = v -> t || n.
Proof.
introv H. induction H; intros; subst.
apply E_Const. apply* step__eval.
Qed.
(** Exercise: using the lemma above, simplify the proof of
the result [multistep__eval]. You should use the tactics
[introv], [inverts], [split*] and [apply*].
The solution is 2 lines long. *)
Theorem multistep__eval' : forall t v,
normal_form_of t v -> exists n, v = C n /\ t || n.
Proof.
introv Hnorm. inverts Hnorm.
rewrite nf_same_as_value in H0. inverts H0.
exists n. split*. apply* multistep_eval_ind.
Qed.
(** If we try to combine the two proofs into a single one,
we will likely fail, because of a limitation of the
[induction] tactic. Indeed, this tactic looses
information when applied to a predicate whose arguments
are not reduced to variables, such as [t ==>* (C n)].
You will thus need to use the more powerful tactic called
[dependent induction]. This tactic is available only after
importing the [Program] library, as shown below. *)
Require Import Program.
(** Exercise: prove the lemma [multistep__eval] without invoking
the lemma [multistep_eval_ind], that is, by inlining the proof
by induction involved in [multistep_eval_ind], using the
tactic [dependent induction] instead of [induction].
The solution is 5 lines long. *)
Theorem multistep__eval'' : forall t v,
normal_form_of t v -> exists n, v = C n /\ t || n.
Proof.
introv Hnorm. inverts Hnorm.