Mercurial > cpdt > repo
diff src/CpdtTactics.v @ 389:76e1dfcb86eb
Comment CpdtTactics.v
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Thu, 12 Apr 2012 18:41:32 -0400 |
parents | d1276004eec9 |
children | 539ed97750bb |
line wrap: on
line diff
--- a/src/CpdtTactics.v Thu Apr 12 16:27:28 2012 -0400 +++ b/src/CpdtTactics.v Thu Apr 12 18:41:32 2012 -0400 @@ -1,4 +1,4 @@ -(* Copyright (c) 2008-2011, Adam Chlipala +(* Copyright (c) 2008-2012, Adam Chlipala * * This work is licensed under a * Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 @@ -14,13 +14,16 @@ Set Implicit Arguments. +(** A version of [injection] that does some standard simplifications afterward: clear the hypothesis in question, bring the new facts above the double line, and attempt substitution for known variables. *) Ltac inject H := injection H; clear H; intros; try subst. +(** Try calling tactic function [f] on all hypotheses, keeping the first application that doesn't fail. *) Ltac appHyps f := match goal with | [ H : _ |- _ ] => f H end. +(** Succeed iff [x] is in the list [ls], represented with left-associated nested tuples. *) Ltac inList x ls := match ls with | x => idtac @@ -28,12 +31,14 @@ | (?LS, _) => inList x LS end. +(** Try calling tactic function [f] on every element of tupled list [ls], keeping the first call not to fail. *) Ltac app f ls := match ls with | (?LS, ?X) => f X || app f LS || fail 1 | _ => f ls end. +(** Run [f] on every element of [ls], not just the first that doesn't fail. *) Ltac all f ls := match ls with | (?LS, ?X) => f X; all f LS @@ -41,15 +46,28 @@ | _ => f ls end. +(** Workhorse tactic to simplify hypotheses for a variety of proofs. + * Argument [invOne] is a tuple-list of predicates for which we always do inversion automatically. *) Ltac simplHyp invOne := + (** Helper function to do inversion on certain hypotheses, where [H] is the hypothesis and [F] its head symbol *) let invert H F := - inList F invOne; (inversion H; fail) - || (inversion H; [idtac]; clear H; try subst) in + (** We only proceed for those predicates in [invOne]. *) + inList F invOne; + (** This case covers an inversion that succeeds immediately, meaning no constructors of [F] applied. *) + (inversion H; fail) + (** Otherwise, we only proceed if inversion eliminates all but one constructor case. *) + || (inversion H; [idtac]; clear H; try subst) in + match goal with + (** Eliminate all existential hypotheses. *) | [ H : ex _ |- _ ] => destruct H + (** Find opportunities to take advantage of injectivity of data constructors, for several different arities. *) | [ H : ?F ?X = ?F ?Y |- ?G ] => + (** This first branch of the [||] fails the whole attempt iff the arguments of the constructor applications are already easy to prove equal. *) (assert (X = Y); [ assumption | fail 1 ]) + (** If we pass that filter, then we use injection on [H] and do some simplification as in [inject]. + * The odd-looking check of the goal form is to avoid cases where [injection] gives a more complex result because of dependent typing, which we aren't equipped to handle here. *) || (injection H; match goal with | [ |- X = Y -> G ] => @@ -64,63 +82,84 @@ try clear H; intros; try subst end) + (** Consider some different arities of a predicate [F] in a hypothesis that we might want to invert. *) | [ H : ?F _ |- _ ] => invert H F | [ H : ?F _ _ |- _ ] => invert H F | [ H : ?F _ _ _ |- _ ] => invert H F | [ H : ?F _ _ _ _ |- _ ] => invert H F | [ H : ?F _ _ _ _ _ |- _ ] => invert H F + (** Use an (axiom-dependent!) inversion principle for dependent pairs, from the standard library. *) | [ H : existT _ ?T _ = existT _ ?T _ |- _ ] => generalize (inj_pair2 _ _ _ _ _ H); clear H + + (** If we're not ready to use that principle yet, try the standard inversion, which often enables the previous rule. *) | [ H : existT _ _ _ = existT _ _ _ |- _ ] => inversion H; clear H + (** Similar logic to the cases for constructor injectivity above, but specialized to [Some], since the above cases won't deal with polymorphic constructors. *) | [ H : Some _ = Some _ |- _ ] => injection H; clear H end. +(** Find some hypothesis to rewrite with, ensuring that [auto] proves all of the extra subgoals added by [rewrite]. *) Ltac rewriteHyp := match goal with - | [ H : _ |- _ ] => rewrite H; auto; [idtac] + | [ H : _ |- _ ] => rewrite H by solve [ auto ] end. +(** Combine [autorewrite] with automatic hypothesis rewrites. *) Ltac rewriterP := repeat (rewriteHyp; autorewrite with core in *). - Ltac rewriter := autorewrite with core in *; rewriterP. +(** This one is just so darned useful, let's add it as a hint here. *) Hint Rewrite app_ass. +(** Devious marker predicate to use for encoding state within proof goals *) Definition done (T : Type) (x : T) := True. +(** Try a new instantiation of a universally quantified fact, proved by [e]. + * [trace] is an accumulator recording which instantiations we choose. *) Ltac inster e trace := + (** Does [e] have any quantifiers left? *) match type of e with | forall x : _, _ => + (** Yes, so let's pick the first context variable of the right type. *) match goal with | [ H : _ |- _ ] => inster (e H) (trace, H) | _ => fail 2 end | _ => + (** No more quantifiers, so now we check if the trace we computed was already used. *) match trace with | (_, _) => + (** We only reach this case if the trace is nonempty, ensuring that [inster] fails if no progress can be made. *) match goal with - | [ H : done (trace, _) |- _ ] => fail 1 + | [ H : done (trace, _) |- _ ] => + (** Uh oh, found a record of this trace in the context! Abort to backtrack to try another trace. *) + fail 1 | _ => + (** What is the type of the proof [e] now? *) let T := type of e in match type of T with | Prop => + (** [e] should be thought of as a proof, so let's add it to the context, and also add a new marker hypothesis recording our choice of trace. *) generalize e; intro; - assert (done (trace, tt)); [constructor | idtac] + assert (done (trace, tt)) by constructor | _ => + (** [e] is something beside a proof. Better make sure no element of our current trace was generated by a previous call to [inster], or we might get stuck in an infinite loop! (We store previous [inster] terms in second positions of tuples used as arguments to [done] in hypotheses. Proofs instantiated by [inster] merely use [tt] in such positions.) *) all ltac:(fun X => match goal with | [ H : done (_, X) |- _ ] => fail 1 | _ => idtac end) trace; + (** Pick a new name for our new instantiation. *) let i := fresh "i" in (pose (i := e); - assert (done (trace, i)); [constructor | idtac]) + assert (done (trace, i)) by constructor) end end end end. +(** After a round of application with the above, we will have a lot of junk [done] markers to clean up; hence this tactic. *) Ltac un_done := repeat match goal with | [ H : done _ |- _ ] => clear H @@ -128,29 +167,49 @@ Require Import JMeq. +(** A more parameterized version of the famous [crush]. Extra arguments are: + * - A tuple-list of lemmas we try [inster]-ing + * - A tuple-list of predicates we try inversion for *) Ltac crush' lemmas invOne := - let sintuition := simpl in *; intuition; try subst; repeat (simplHyp invOne; intuition; try subst); try congruence in - let rewriter := autorewrite with core in *; - repeat (match goal with - | [ H : ?P |- _ ] => - match P with - | context[JMeq] => fail 1 - | _ => (rewrite H; []) - || (rewrite H; [ | solve [ crush' lemmas invOne ] ]) - || (rewrite H; [ | solve [ crush' lemmas invOne ] | solve [ crush' lemmas invOne ] ]) - end - end; autorewrite with core in *) - in (sintuition; rewriter; - match lemmas with - | false => idtac - | _ => repeat ((app ltac:(fun L => inster L L) lemmas || appHyps ltac:(fun L => inster L L)); - repeat (simplHyp invOne; intuition)); un_done - end; sintuition; rewriter; sintuition; try omega; try (elimtype False; omega)). + (** A useful combination of standard automation *) + let sintuition := simpl in *; intuition; try subst; + repeat (simplHyp invOne; intuition; try subst); try congruence in + (** A fancier version of [rewriter] from above, which uses [crush'] to discharge side conditions *) + let rewriter := autorewrite with core in *; + repeat (match goal with + | [ H : ?P |- _ ] => + match P with + | context[JMeq] => fail 1 (** JMeq is too fancy to deal with here. *) + | _ => rewrite H by crush' lemmas invOne + end + end; autorewrite with core in *) in + + (** Now the main sequence of heuristics: *) + (sintuition; rewriter; + match lemmas with + | false => idtac (** No lemmas? Nothing to do here *) + | _ => + (** Try a loop of instantiating lemmas... *) + repeat ((app ltac:(fun L => inster L L) lemmas + (** ...or instantiating hypotheses... *) + || appHyps ltac:(fun L => inster L L)); + (** ...and then simplifying hypotheses. *) + repeat (simplHyp invOne; intuition)); un_done + end; + sintuition; rewriter; sintuition; + (** End with a last attempt to prove an arithmetic fact with [omega], or prove any sort of fact in a context that is contradictory by reasoning that [omega] can do. *) + try omega; try (elimtype False; omega)). + +(** [crush] instantiates [crush'] with the simplest possible parameters. *) Ltac crush := crush' false fail. +(** * Wrap Program's [dependent destruction] in a slightly more pleasant form *) + Require Import Program.Equality. +(** Run [dependent destruction] on [E] and look for opportunities to simplify the result. + The weird introduction of [x] helps get around limitations of [dependent destruction], in terms of which sorts of arguments it will accept (e.g., variables bound to hypotheses within Ltac [match]es). *) Ltac dep_destruct E := let x := fresh "x" in remember E as x; simpl in x; dependent destruction x; @@ -158,11 +217,14 @@ | [ H : _ = E |- _ ] => rewrite <- H in *; clear H end. +(** Nuke all hypotheses that we can get away with, without invalidating the goal statement. *) Ltac clear_all := repeat match goal with | [ H : _ |- _ ] => clear H end. +(** Instantiate a quantifier in a hypothesis [H] with value [v], or, if [v] doesn't have the right type, with a new unification variable. + * Also prove the lefthand sides of any implications that this exposes, simplifying [H] to leave out those implications. *) Ltac guess v H := repeat match type of H with | forall x : ?T, _ => @@ -171,17 +233,18 @@ (let H' := fresh "H'" in assert (H' : T); [ solve [ eauto 6 ] - | generalize (H H'); clear H H'; intro H ]) + | specialize (H H'); clear H' ]) || fail 1 | _ => - (generalize (H v); clear H; intro H) + specialize (H v) || let x := fresh "x" in evar (x : T); - let x' := eval cbv delta [x] in x in - clear x; generalize (H x'); clear H; intro H + let x' := eval unfold x in x in + clear x; specialize (H x') end end. +(** Version of [guess] that leaves the original [H] intact *) Ltac guessKeep v H := let H' := fresh "H'" in generalize H; intro H'; guess v H'.