Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shorter notation for matching hypotheses in Coq?

I find myself often wanting to refer to hypotheses by their type rather than by their name; especially in proofs with inversions on semantic rules, i.e., rules with several cases each of which may have multiple antecedents.

I know how to do this with match goal with ..., as in the following trivial example.

Lemma l0:
  forall P1 P2,
    P1 \/ (P1 = P2) ->
    P2 ->
    P1.
Proof.
  intros.
  match goal with H:_ \/ _ |- _ => destruct H as [H1|H2] end.
  assumption.
  match goal with H: _ = _ |- _ => rewrite H end.
  assumption.
Qed.

Is there a more concise way? Or a better approach?

(Introduction patterns, like intros [???? HA HB|??? HA|????? HA HB HC HD], are not an option—I am tired of finding the right number of ?s!)

For instance, is it possible to write a grab tactic to combine a pattern and a tactic, as in

  grab [H:P1 \/ _] => rename H into HH.
  grab [H:P1 \/ _] => destruct H into [H1|H2].

  grab [P1 \/ _] => rename it into HH.
  grab [P1 \/ _] => destruct it into [H1|H2].

From my understanding of Tactic Notations, it is not possible to have a cpattern as an argument, but maybe there is another way?

Ideally, I would like to be able to use an hypothesis pattern instead of an identifier in any tactic as in Isabelle:

rename ⟨P1 \/ _⟩ into HH.
destruct ⟨P1 \/ _⟩ as [H1|H2].
rewrite ⟨P1 = _⟩.

But I imagine this to be quite an invasive change.

like image 788
tbrk Avatar asked May 05 '19 13:05

tbrk


People also ask

How do you duplicate a hypothesis in Coq?

assert (HA := l1 H). assert (HB := l2 H). You can also do something like assert (H' := H). to explicitly copy H into H' , although you can usually resort to a more direct way for getting what you want. Save this answer.

What does inversion do coq?

inversion: Deduces equalities that must be true given an equality between two constructors. left / right: Replaces a goal consisting of a disjunction P \/ Q with just P or Q .

What does auto do in Coq?

auto tries a hint when the conclusion of the current goal matches its pattern or when the hint has no pattern.

Which goals can you conclude with reflexivity?

reflexivity. Use reflexivity when your goal is to prove that something equals itself. In this example we will prove that any term x of type Set is equal to itself.


1 Answers

You can iterate over all the assumptions until you find a matching one:

Tactic Notation "summon" uconstr(ty) "as" ident(id) :=
  match goal with H : _ |- _ => pose (id := H : ty) end.

The trick is that you take the type to be found not as a pattern, but, well, as a type :). Specifically, if you issue something like summon (P _) as id, then Coq will take the _ as an unsolved existential variable. In turn, each assumption will be typechecked against P _, trying to instantiate that hole along the way. When one succeeds, the pose names it id. The iteration arises because match goal will keep retrying with different matches until something sticks or everything fails.

You can define a form without as that just names the found thing it (while kicking anything else out):

Tactic Notation "summon" uconstr(ty) :=
  let new_it := fresh "it"
   in try (rename it into new_it); summon ty as it.

Ta-da!

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  summon (_ \/ _).
  destruct it.
  assumption.
  summon (_ = _).
  rewrite it.
  assumption.
Qed.

You can also get your => syntax. I don't think it's terribly useful, but...

(* assumption of type ty is summoned into id for the duration of tac
   anything that used to be called id is saved and restored afterwards,
   if possible. *)
Tactic Notation "summon" uconstr(ty) "as" ident(id) "=>" tactic(tac) :=
  let saved_id := fresh id
   in try (rename id into saved_id);
      summon ty as id; tac;
      try (rename saved_id into id).

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  summon (_ \/ _) as H => destruct H.
  assumption.
  summon (_ = _) as H => rewrite H.
  assumption.
Qed.

Old answer

(You may want to read this, because the above solution is really a variant of this one, and there's more explanation here.)

You can summon an assumption matching a type pattern into a name with eassert (name : ty) by eassumption..

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  eassert (HH : _ \/ _) by eassumption.
  destruct HH.
  assumption.
  eassert (HH : _ = _) by eassumption.
  rewrite HH.
  assumption.
Qed.

Why is this an improvement? Because the _ \/ _ and _ = _ are now full types, not just patterns. They just contain unsolved existential variables. Between eassert and eassumption, these variables get solved at the same time the matching assumption is located. Tactic notations can definitely work with types (i.e. terms). Sadly, there appears to be a bit of a mishap in the parsing rules. Specifically, the tactic notation needs an untyped term (so we don't try and fail to resolve the variables too early), so we need uconstr, but there's no luconstr, meaning we're forced to add extraneous parentheses. To avoid bracket-mania, I've reworked the syntax of your grab. I'm also not entirely sure if your => syntax makes much sense, because why not just bring the name into scope for good, instead of only on the =>, as you seem to imply?

Tactic Notation "summon" uconstr(ty) "as" ident(id) :=
  eassert (id : ty) by eassumption.

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  summon (_ \/ _) as HH.
  destruct HH.
  assumption.
  summon (_ = _) as HH.
  rewrite HH.
  assumption.
Qed.

You can make summon-sans-as name the found assumption it, while booting anything else under that name out.

Tactic Notation "summon" uconstr(ty) "as" ident(id) :=
  eassert (id : ty) by eassumption.

Tactic Notation "summon" uconstr(ty) :=
  let new_it := fresh "it"
   in (try (rename it into new_it); summon ty as it).

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  (* This example is actually a bad demonstration of the name-forcing behavior
     because destruct-ion, well, destroys.
     Save the summoned proof under the name it, but destroy it from another,
     then observe the way the second summon shoves the original it into it0. *)
  summon (_ \/ _) as prf.
  pose (it := prf).
  destruct prf.
  assumption.
  summon (_ = _).
  rewrite it.
  assumption.
Qed.

Idiomatically, that would really just be

Lemma l0 : forall P1 P2, P1 \/ (P1 = P2) -> P2 -> P1.
Proof.
  intros.
  summon (_ \/ _).
  destruct it.
  assumption.
  summon (_ = _).
  rewrite it.
  assumption.
Qed.

I believe that you could go and create a bunch of specialized Tactic Notations to replace the ident arguments in destruct, rewrite, etc. with these holey-type uconstrs, if you really wanted to. Indeed, summon _ as _ is almost your modified rename _ into _.

Another caveat: assert is opaque; the definitions generated by summon look like new assumptions that don't reveal that they are equal to one of the old ones. Something like refine (let it := _ in _) or pose should be used to rectify this, but my Ltac-fu is not strong enough to do this. See also: this issue advocating for a literal transparent assert.

(The new answer solves this caveat.)

like image 97
HTNW Avatar answered Sep 28 '22 18:09

HTNW