10.2 Tactic Macros

10.2 Tactic Macros

The simplest way to introduce it is to define it as new a tactic macro, as follows:

Coq < Tactic Definition DecideEq [$a $b] :=
Coq < [<:tactic:<Destruct $a;
Coq < Destruct $b;
Coq < (Left;Reflexivity) Orelse (Right;Discriminate)>>].


The general pattern of the proof is abstracted away using the tacticals ``;'' and Orelse, and introducing two parameters for the names of the arguments to be analyzed.

Once defined, this tactic can be called like any other tactic, just supplying the list of terms corresponding to its real arguments. Let us revisit the proof of the former theorems using the new tactic DecideEq:

Coq < Theorem decideBool : (x,y:bool){x=y}+{~x=y}.

Coq < DecideEq x y.

Coq < Defined.
Coq < Theorem decideColor : (c1,c2:Color){c1=c2}+{~c1=c2}.

Coq < DecideEq c1 c2.

Coq < Defined.


In general, the command Tactic Definition associates a name to a parameterized tactic expression, built up from the tactics and tacticals that are already available. The general syntax rule for this command is the following:
Tactic Definition tactic-name [$id1... $idn]
  := [<:tactic:< tactic-expression >>]


This command provides a quick but also very primitive mechanism for introducing new tactics. It does not support recursive definitions, and the arguments of a tactic macro are restricted to term expressions. Moreover, there is no static checking of the definition other than the syntactical one. Any error in the definition of the tactic ---for instance, a call to an undefined tactic--- will not be noticed until the tactic is called.

Let us illustrate the weakness of this way of introducing new tactics trying to extend our proof procedure to work on a larger class of inductive types. Consider for example the decidability of equality for pairs of booleans and colors:

Coq < Theorem decideBoolXColor : (p1,p2:bool*Color){p1=p2}+{~p1=p2}.


The proof still proceeds by a double case analysis, but now the constructors of the type take two arguments. Therefore, the sub-goals that can not be solved by discrimination need further considerations about the equality of such arguments:

Coq < Destruct p1;
Coq < Destruct p2; Try (Right;Discriminate);Intros.
1 subgoal
  
  p1 : bool*Color
  b : bool
  c : Color
  p2 : bool*Color
  b0 : bool
  c0 : Color
  ============================
   {(b,c)=(b0,c0)}+{~(b,c)=(b0,c0)}


The half of the disjunction to be chosen depends on whether or not b=b0 and c=c0. These equalities can be decided automatically using the previous lemmas about booleans and colors. If both equalities are satisfied, then it is sufficient to rewrite b into b0 and c into c0, so that the left half of the goal follows by reflexivity. Otherwise, the right half follows by first contraposing the disequality, and then applying the injectiveness of the pairing constructor.

As the cases associated to each argument of the pair are very similar, a tactic macro can be introduced to abstract this part of the proof:

Coq < Hint decideBool decideColor.

Coq < Tactic Definition SolveArg [$t1 $t2] :=
Coq < [<:tactic:<
Coq < ElimType {$t1=$t2}+{~$t1=$t2};
Coq < [(Intro equality;Rewrite equality;Clear equality) |
Coq < (Intro diseq; Right; Red; Intro absurd;
Coq < Apply diseq;Injection absurd;Trivial) |
Coq < Auto]>>].


This tactic is applied to each corresponding pair of arguments of the arguments, until the goal can be solved by reflexivity:

Coq < SolveArg b b0;
Coq < SolveArg c c0;
Coq < Left; Reflexivity.

Coq < Defined.


Therefore, a more general strategy for deciding the property (x,y:R){x=y}+{¬ x =y} on R can be sketched as follows:
  1. Eliminate x and then y.
  2. Try discrimination to solve those goals where x and y has been introduced by different constructors.
  3. If x and y have been introduced by the same constructor, then iterate the tactic SolveArg for each pair of arguments.
  4. Finally, solve the left half of the goal by reflexivity.


The implementation of this stronger proof strategy needs to perform a term decomposition, in order to extract the list of arguments of each constructor. It also requires the introduction of recursively defined tactics, so that the SolveArg can be iterated on the lists of arguments. These features are not supported by the Tactic Definition command. One possibility could be extended this command in order to introduce recursion, general parameter passing, pattern-matching, etc, but this would quickly lead us to introduce the whole Ocaml into Coq(This is historically true. In fact, Ocaml is a direct descendent of ML, a functional programming language conceived language for programming the tactics of the theorem prover LCF.). Instead of doing this, we prefer to give to the user the possibility of writing his/her own tactics directly in Ocaml, and then to link them dynamically with Coq's code. This requires a minimal knowledge about Coq's implementation. The next section provides an overview of Coq's architecture.



Retrieved by Memoweb from http://pauillac.inria.fr/coq/doc/node.2.1.1.html at 8/10/98