Home |
Github |
This category refers to tactics that modify the behavior of other tactics. Important phrasing note for this section: a tactical is only a tactical when it doesn’t have all of its arguments. A tactical with all of its arguments is a tactic. Tacticals are heavily utilized in automation because they broaden the capabilities of the tactic language significantly, making it much more expressive.
For an interesting perspective on automation - and good examples of building “one shot proofs” (proofs that utilize tacticals to contain only one proof step) - check out this post by Adam Chlipala.
The try
tactical executes a provided tactic, catching
any errors and always succeeding.
(* Simple usage *)
try reflexivity.
Before
n: nat
=========================
1/1
n + 0 = n
try reflexivity.
After
n: nat
=========================
1/1
n + 0 = n
Alternatively,
try apply add_0_r.
No more goals.
The infix ;
tactical is the sequencing tactical. It
applies the right tactic to all of the goals generated by the left
tactic.
The ;
tactical is binary, so it takes two tactics (we
will say A
and B
) as input. A
is
executed. If A
does not fail and does not solve the goal,
then B
is executed for every goal that results from
applying A
. If A
solves the goal, then
B
is never called and the entire tactic succeeds. This is
useful when A
generates lots of very simple subgoals (like
preconditions of a theorem application) that can all be handled with
another automation tactic.
The ;
tactical is left-associative. Consider the tactic
A; B; C.
If A
generates goals A1
and A2
, then B
will be applied to each. Let’s
say that this results in a state with goals A1'
,
A2'
, and B'
. C
will now be
applied to each of these. This may not always be desired, and so
parentheses can be used to force right-associativity. Consider the
tactic A; (B; C)
. If A
generates goals
A1
and A2
, then B; C
will be
applied to each. The difference may not be crystal-clear in an abstract
example such as this one, so check out the script below. Keep in mind
that the difference is in the resulting state tree from calling these
tactics:
A; B; C
├── A1 /* Call B */
│ └── A1' /* Call C */
│ └── A1''
└── A2 /* Call B */
└── A2' /* Call C */
└── A2''
A;(B;C) /* Call A */
├── A1 /* Call B;C */
│ └── A1''
└── A2 /* Call B;C */
└── A2''
Also keep in mind that this behavior is extremely versatile, the above tree “shortening” use is only one example.
Compare this tactical with Prolog’s
semicolon operator and revel at some neat similarities! For example,
in Coq, A;B
will backtrack if B
fails and
A
can succeed in a different way. The primary
example of a tactic being able to succeed in multiple ways is the constructor
tactic.
(* Simple usage *)
split; reflexivity.
(* Left-associative chain *)
split; simpl; reflxivity.
(* Right-associative chain *)
split; (split; auto).
Before
P, Q: Prop
H: Q
=========================
1/1
P \/ Q
constructor; assumption.
After
No more goals.
Note the definition of or
:
Inductive or (A B : Prop) : Prop :=
| or_introl : A -> A \/ B
| or_intror : B -> A \/ B.
Goal selectors are a category of tacticals that apply a tactic to a specific goal or goals.
There are a number of goal selectors:
all
: Applies the tactic to all goals in focus
in series!
: If only one goal is in focus, apply the tactic. If
not, this tactic failspar
: Applies the tactic to all goals in focus
in parallel. The tactic provided must solve all goals
or do nothing, otherwise this tactic failsn-m
: Applies the tactic to goals with indices between
n
and m
, inclusiveall: simpl.
par: simpl; reflexivity; auto.
!: discriminate.
2-3: auto.
Before
=========================
1/2
True
=========================
2/2
True
all: exact I.
(* or *)
1-2: exact I.
After
No more goals.
Alternatively,
!: exact I.
Error: Expected a single focused goal but 2 goals are focused.
The repeat
tactical repeatedly executes a tactic until
it either fails or causes no change in the goal. If the tactic provided
succeeds, it will be recursively applied to each generated subgoal.
Be careful: if the input tactic never fails, repeat
will
cause an infinite loop! For example, repeat symmetry
or
repeat idtac
will always result in an infinite loop.
(* Simple usage *)
repeat split.
Before
P, Q, R, S: Prop
=========================
1/1
P /\ Q /\ R /\ S
repeat split.
After
P, Q, R, S: Prop
=========================
1/4
P
=========================
2/4
Q
=========================
3/4
R
=========================
4/4
S
The infix ||
tactical tries the first tactic and only
tries the second if the first failed. In other words, ||
executes the first tactic that makes progress on the goal.
(* Simple usage *)
reflexivity || assumption.
Before
P: Prop
H: P
=========================
1/1
P
reflexivity || assumption.
After
No more goals.
now tactic
is simply notation for tactic;
easy
.
now split.
Before
=========================
1/1
True /\ 42 = 14 * 3
now split.
After
No more goals.
The do
tactical accepts a tactic t
and a
natural number n
, applying t
to the goal
n
times. do
fails if one of the applications
of t
fails before n
applications have
occurred.
In my opinion, do
is a difficult tactic to justify. I
find myself using it when using repeat
tends to be
overzealous. For example, if I have a goal with 100 subterms, and I’d
like to apply a tactic t
only to 30 of the subterms
(assuming t
works on individual subterms and not the whole
goal), I’m more likely to use do 30 t
than
repeat t
to prevent the remaining 70 subterms from being
affected.
do 3 (split; [reflexivity | idtac]).
Before
=========================
1/1
1 = 1 /\ 2 = 2 /\ 3 = 3 /\ 4 = 4
do 3 (split; [reflexivity | idtac]).
After
No more goals.