TacticExts - coq/coq GitHub Wiki
Recursion under binders
Using typeclasses, it is possible to recurse under binders. Here is an example of a tactic that recurses under binders to turn a proof of A -> B -> C /\ D
into a proof of A -> B -> C
:
Class ret_and_left {T} (arg : T) {R} := make_recur_ret_and_left : R.
Ltac ret_and_left_helper f :=
let T := type of f in
lazymatch eval hnf in T with
| ?a /\ ?b => exact (proj1 f)
| ?T' -> _
=> exact (fun x' : T' => _ : ret_and_left (f x'))
end.
Hint Extern 0 (@ret_and_left _ ?f _) => ret_and_left_helper f : typeclass_instances.
Arguments ret_and_left / .
Goal forall A B : Prop, (A -> A -> A /\ B) -> True.
intros A B H.
pose (_ : ret_and_left H) as H'; simpl in H'.
(* fun x' x'0 : A => proj1 (H x' x'0) : A -> A -> A *)
NB: In Coq >= 8.5, it will be possible to do this using tactics in terms rather than a separate typeclass for each tactic, and without having to simpl
at the end:
Ltac ret_and_left f :=
let tac := ret_and_left in
let T := type of f in
match eval hnf in T with
| ?a /\ ?b => exact (proj1 f)
| ?T' -> _ =>
let ret := constr:(fun x' : T' => let fx := f x' in
$(tac fx)$) in
let ret' := (eval cbv zeta in ret) in
exact ret'
end.
Goal forall A B : Prop, (A -> A -> A -> A /\ B) -> True.
intros A B H.
pose $(ret_and_left H)$.
(** [fun x' x'0 : A => proj1 (H x' x'0) : A -> A -> A] *)
Dependent case
dcase
is a version of case that remembers the case you are in.
Ltac dcase x := generalize (refl_equal x); pattern x at -1; case x.
NB: This tactic has been integrated in Coq >= 8.1beta under the name case_eq
Expand until
This tactic is useful when carefully unfolding definitions, for instance inductive ones. It also shows the use of tactic notation.
Tactic Notation "expand" reference (t) "until" constr (s):=
let x:=fresh"x" in
(set (x:=s); unfold t; fold t; unfold x).
This has proved useful in a situation like sorted (b :: a :: a0)
on
which unfold sorted; fold sorted
leads to:
cmp b a = true /\
match a0 with
| nil => True
| a'' :: _ => cmp a a'' = true /\ sorted a0
end
There's two levels expanded! Solution was "expand sorted until (a::a0)." (thanks roconnor)
Clean duplicated hypothesis
This tactic removes redundant hypothesis from the context.
Ltac exist_hyp t := match goal with
| H1:t |- _ => idtac
end.
Ltac clean_duplicated_hyps :=
repeat match goal with
| H:?X1 |- _ => clear H; exist_hyp X1
end.
Assert if necessary
This tactic assert a fact only if does not already exists in the context. This is intended to be used in more complex tactics.
Ltac not_exist_hyp t := match goal with
| H1:t |- _ => fail 2
end || idtac.
Ltac assert_if_not_exist H :=
not_exist_hyp H;assert H.
RewriteAll
NB: A similar rewrite_all
has been integrated in Coq >= 8.1beta (see file theories/Init/Tactics.v
). Moreover, in the release following 8.1beta, the newly allowed syntax rewrite ... in *
permits to define rewrite_all
with a simple repeat rewrite ... in *
.
Given an assumption H : t1 = t2
, the tactic rewrite_all H
replaces t1
with t2
both in goal and local context. We have to take care that H
does not rewrite itself, for then we'd get H : t2 = t2
, and a loop is entered.
Ltac rewrite_in_cxt H :=
let T := type of H in
match T with
| ?t1 = ?t2 =>
repeat
(
generalize H; clear H;
match goal with
| id : context[t1] |- _ =>
intro H; rewrite H in id
end
)
end.
Ltac rewrite_all H :=
rewrite_in_cxt H; rewrite H.
Ltac replace_in_cxt t1 t2 :=
let H := fresh "H" in
(cut (t1 = t2); [ intro H; rewrite_in_cxt H; clear H | idtac ]).
Ltac replace_all t1 t2 :=
let H := fresh "H" in
(cut (t1 = t2); [ intro H; rewrite_all H; clear H | idtac ]).
RewriteAll, expert version
Given an assumption H : t1 = t2
, the tactic rewrite_all H
replaces t1
with t2
both in goal and local context. We have to take care that H
does not rewrite itself, for then we'd get H : t2 = t2
, and a loop is entered; this version generates a smarter proof term than the previous one.
Ltac rewrite_all H :=
match type of H with
| ?t1 = ?t2 =>
let rec aux H :=
match goal with
| id : context [t1] |- _ =>
match type of id with
| t1 = t2 => fail 1
| _ => generalize id;clear id; try aux H; intro id
end
| _ => try rewrite H
end in
aux H
end.
Decide Equality
Coq's decide equality should be more accepting. It ought to behave more like the following.
Ltac decideEquality :=
match goal with
| |- forall x y, {x = y}+{~x=y} => decide equality
| |- {?a=?b}+{~?a=?b} => decide equality a b
| |- {~?a=?b}+{?a=?b} => cut ({a=b}+{~a=b});[tauto | decide equality a b]
end.