(** DeepSpec Summer School 2017 * Lectures by Adam Chlipala on proof automation * Lecture 1: introduction to proof scripting and the Ltac language * Author: Adam Chlipala * License: https://creativecommons.org/licenses/by-nc-nd/4.0/ * Much of the material comes from CPDT by the same author. *) (** * Ltac Programming Basics *) (* We have already seen a few examples of Ltac programs, without much explanation. * Ltac is the proof scripting language built into Coq. Actually, every * primitive step in our proofs has been a (degenerate, small) Ltac program. * Let's take a bottom-up look at more Ltac features. * * We've seen [match goal] tactics a few times so far. They allow syntactic * inspection of hypothesis and conclusion formulas of current goals, where we * pick tactics to run based on what we find. Here's a simple example to * find an [if] and do a case split based on its test expression. *) Ltac find_if := match goal with | [ |- if ?X then _ else _ ] => destruct X end. (* Here's a proof that becomes trivial, given [find_if]. You can imagine a * whole family of similar theorems that also become trivial. *) Theorem hmm : forall (a b c : bool), if a then if b then True else True else if c then True else True. Proof. intros. repeat find_if. (* Note [repeat] for "run over and over until you can't * progress." *) trivial. trivial. trivial. trivial. (* Let's write that again with more automation. *) Restart. intros; repeat find_if; trivial. Qed. (* Another very useful Ltac building block is *context patterns*. *) Ltac find_if_inside := match goal with | [ |- context[if ?X then _ else _] ] => destruct X end. (* The behavior of this tactic is to find any subterm of the conclusion that is * an [if] and then [destruct] the test expression. This version subsumes * [find_if]. The general behavior of [context] (an Ltac keyword) is to allow * matching in arbitrary subterms. *) Theorem hmm' : forall (a b c : bool), if a then if b then True else True else if c then True else True. Proof. intros; repeat find_if_inside; trivial. Qed. (* We can also use [find_if_inside] to prove goals that [find_if] does not * simplify sufficiently. *) Theorem hmm2 : forall (a b : bool), (if a then 42 else 42) = (if b then 42 else 42). Proof. intros; repeat find_if_inside; reflexivity. Qed. (** * Implementing some of [tauto] ourselves *) (* In class, we develop our own implementation of [tauto] one feature * at a time, but here's just the final product. To understand it, we print * the definitions of the logical connectives. Interestingly enough, they are * special cases of the machinery we met last time for inductive relations! *) Print True. Print False. Locate "/\". Print and. Locate "\/". Print or. (* Implication ([->]) is built into Coq, so nothing to look up there. *) Ltac my_tauto := repeat match goal with | [ H : ?P |- ?P ] => exact H | [ |- True ] => constructor | [ |- _ /\ _ ] => constructor | [ |- _ -> _ ] => intro | [ H : False |- _ ] => destruct H | [ H : _ /\ _ |- _ ] => destruct H | [ H : _ \/ _ |- _ ] => destruct H | [ H1 : ?P -> ?Q, H2 : ?P |- _ ] => specialize (H1 H2) end. Section propositional. Variables P Q R : Prop. Theorem propositional : (P \/ Q \/ False) /\ (P -> Q) -> True /\ Q. Proof. my_tauto. Qed. End propositional. (* [match goal] has useful backtracking semantics. When one rule fails, we * backtrack automatically to the next one. *) (* For instance, this (unnecessarily verbose) proof script works: *) Theorem m1 : True. Proof. match goal with | [ |- _ ] => intro | [ |- True ] => constructor end. Qed. (* The example shows how failure can move to a different pattern within a * [match]. Failure can also trigger an attempt to find _a different way of * matching a single pattern_. Consider another example: *) Theorem m2 : forall P Q R : Prop, P -> Q -> R -> Q. Proof. intros; match goal with | [ H : _ |- _ ] => idtac H end. (* Coq prints "[H1]". By applying [idtac] with an argument, a convenient * debugging tool for "leaking information out of [match]es," we see that * this [match] first tries binding [H] to [H1], which cannot be used to prove * [Q]. Nonetheless, the following variation on the tactic succeeds at * proving the goal: *) match goal with | [ H : _ |- _ ] => idtac H; exact H end. Qed. (* The tactic first unifies [H] with [H1], as before, but [exact H] fails in * that case, so the tactic engine searches for more possible values of [H]. * Eventually, it arrives at the correct value, so that [exact H] and the * overall tactic succeed. *) (* Let's try some more ambitious reasoning, with quantifiers. We'll be * instantiating quantified facts heuristically. If we're not careful, we get * in a loop repeating the same instantiation forever. We'll need a way to * check that a fact is not already known. Here's a tactic: *) Ltac notHyp P := match goal with | [ _ : P |- _ ] => fail 1 (* A hypothesis already asserts this fact. *) | _ => match P with | ?P1 /\ ?P2 => (* Check each conjunct of [P] separately, since they might be known by * different means. *) first [ notHyp P1 | notHyp P2 | fail 2 ] | _ => idtac (* If we manage to get this far, then we found no redundancy, so * declare success. *) end end. (* The number for [fail N] indicates failing at the backtracking point [N] * levels out from where we are. [first] applies the first tactic that does not * fail. *) (* This tactic adds a fact to the context, only if it is not not already * present. *) Ltac extend pf := let t := type of pf in notHyp t; pose proof pf. (* With these tactics defined, we can write a tactic [completer] for, among * other things, adding to the context all consequences of a set of simple * first-order formulas. *) Ltac completer := repeat match goal with | [ H : _ /\ _ |- _ ] => destruct H | [ H : ?P -> ?Q, H' : ?P |- _ ] => specialize (H H') | [ H : forall x, ?P x -> _, H' : ?P ?X |- _ ] => extend (H X H') | [ |- _ /\ _ ] => constructor | [ |- forall x, _ ] => intro | [ |- _ -> _ ] => intro end. Section firstorder. Variable A : Set. Variables P Q R S : A -> Prop. Hypothesis H1 : forall x, P x -> Q x /\ R x. Hypothesis H2 : forall x, R x -> S x. Theorem fo : forall (y x : A), P x -> S x. Proof. completer. assumption. Qed. End firstorder. (** * Recursive Proof Search *) (* Let's work on a tactic to try all possible instantiations of quantified * hypotheses, attempting to find out where the goal becomes obvious. *) Ltac inster n := intuition; match n with | S ?n' => match goal with | [ H : forall x : ?T, _, y : ?T |- _ ] => pose proof (H y); idtac H; idtac y; inster n' end end. (* Important: when one recursive call fails, the backtracking semantics of * [match goal] cause us to try the next instantiation! *) Section test_inster. Variable A : Set. Variables P Q : A -> Prop. Variable f : A -> A. Variable g : A -> A -> A. Hypothesis H1 : forall x y, P (g x y) -> Q (f x). Theorem test_inster : forall x, P (g x x) -> Q (f x). Proof. inster 2. Qed. Hypothesis H3 : forall u v, P u /\ P v /\ u <> v -> P (g u v). Hypothesis H4 : forall u, Q (f u) -> P u /\ P (f u). Theorem test_inster2 : forall x y, x <> y -> P x -> Q (f y) -> Q (f x). Proof. inster 3. Qed. End test_inster. (** * Creating Unification Variables *) (* A final useful ingredient in tactic crafting is the ability to allocate new * unification variables explicitly. Before we are ready to write a tactic, we * can try out its ingredients one at a time. *) Theorem t5 : (forall x : nat, S x > x) -> 2 > 1. Proof. intros. evar (y : nat). let y' := eval unfold y in y in clear y; specialize (H y'). apply H. Qed. Ltac newEvar T k := let x := fresh "x" in evar (x : T); let x' := eval unfold x in x in clear x; k x'. Ltac insterU H := repeat match type of H with | forall x : ?T, _ => newEvar T ltac:(fun y => specialize (H y)) end. Theorem t5' : (forall x : nat, S x > x) -> 2 > 1. Proof. intro H. insterU H. apply H. Qed. (* This particular example is somewhat silly, since [apply] by itself would have * solved the goal originally. Separate forward reasoning is more useful on * hypotheses that end in existential quantifications. Before we go through an * example, it is useful to define a variant of [insterU] that does not clear * the base hypothesis we pass to it. *) Ltac insterKeep H := let H' := fresh "H'" in pose proof H as H'; insterU H'. Section t6. Variables A B : Type. Variable P : A -> B -> Prop. Variable f : A -> A -> A. Variable g : B -> B -> B. Hypothesis H1 : forall v, exists u, P v u. Hypothesis H2 : forall v1 u1 v2 u2, P v1 u1 -> P v2 u2 -> P (f v1 v2) (g u1 u2). Theorem t6 : forall v1 v2, exists u1, exists u2, P (f v1 v2) (g u1 u2). Proof. intros. do 2 insterKeep H1. repeat match goal with | [ H : ex _ |- _ ] => destruct H end. eauto. Qed. End t6. (* Here's an example where something bad happens. *) Section t7. Variables A B : Type. Variable Q : A -> Prop. Variable P : A -> B -> Prop. Variable f : A -> A -> A. Variable g : B -> B -> B. Hypothesis H1 : forall v, Q v -> exists u, P v u. Hypothesis H2 : forall v1 u1 v2 u2, P v1 u1 -> P v2 u2 -> P (f v1 v2) (g u1 u2). Theorem t7 : forall v1 v2, Q v1 -> Q v2 -> exists u1, exists u2, P (f v1 v2) (g u1 u2). Proof. intros; do 2 insterKeep H1; repeat match goal with | [ H : ex _ |- _ ] => destruct H end; eauto. (* Oh, two trivial goals remain. *) Unshelve. assumption. assumption. Qed. End t7. (* Why did we need to do that extra work? The [forall] rule was also matching * implications! *) Module Import FixedInster. Ltac insterU tac H := repeat match type of H with | forall x : ?T, _ => match type of T with | Prop => (let H' := fresh "H'" in assert (H' : T) by solve [ tac ]; specialize (H H'); clear H') || fail 1 | _ => newEvar T ltac:(fun y => specialize (H y)) end end. Ltac insterKeep tac H := let H' := fresh "H'" in pose proof H as H'; insterU tac H'. End FixedInster. Section t7'. Variables A B : Type. Variable Q : A -> Prop. Variable P : A -> B -> Prop. Variable f : A -> A -> A. Variable g : B -> B -> B. Hypothesis H1 : forall v, Q v -> exists u, P v u. Hypothesis H2 : forall v1 u1 v2 u2, P v1 u1 -> P v2 u2 -> P (f v1 v2) (g u1 u2). Theorem t7' : forall v1 v2, Q v1 -> Q v2 -> exists u1, exists u2, P (f v1 v2) (g u1 u2). Proof. intros. do 2 insterKeep ltac:(idtac; match goal with | [ H : Q ?v |- _ ] => match goal with | [ _ : context[P v _] |- _ ] => fail 1 | _ => apply H end end) H1; repeat match goal with | [ H : ex _ |- _ ] => destruct H end; eauto. Qed. End t7'. Theorem t8 : exists p : nat * nat, fst p = 3. Proof. econstructor. instantiate (1 := (3, 2)). reflexivity. Qed. (* A way that plays better with automation: *) Ltac equate x y := let dummy := constr:(eq_refl x : x = y) in idtac. Theorem t9 : exists p : nat * nat, fst p = 3. Proof. econstructor; match goal with | [ |- fst ?x = 3 ] => equate x (3, 2) end. reflexivity. Qed.