In coq, the destruct tactic has a variant accepting an "conjunctive disjunctive introduction pattern" that allows the user to assign names to introduced variables, even when unpacking complex inductive types.
The Ltac language in coq allows the user to write custom tactics. I'd like to write (in truth, maintain) a tactic that does something before handing control off to destruct.
I would like my custom tactic to allow (or require, whichever is easier) the user to supply an introduction pattern that my tactic can hand to destruct.
What Ltac syntax accomplishes this?
You can use tactic notations, described in the reference manual. For instance,
Tactic Notation "foo" simple_intropattern(bar) :=
match goal with
| H : ?A /\ ?B |- _ =>
destruct H as bar
end.
Goal True /\ True /\ True -> True.
intros. foo (HA & HB & HC).
The simple_intropattern directive instructs Coq to interpret bar as an intro pattern. Thus, the call to foo results in calling destruct H as (HA & HB & HC).
Here's a longer example showing a more complex introduction pattern.
Tactic Notation "my_destruct" hyp(H) "as" simple_intropattern(pattern) :=
destruct H as pattern.
Inductive wondrous : nat -> Prop :=
| one : wondrous 1
| half : forall n k : nat, n = 2 * k -> wondrous k -> wondrous n
| triple_one : forall n k : nat, 3 * n + 1 = k -> wondrous k -> wondrous n.
Lemma oneness : forall n : nat, n = 0 \/ wondrous n.
Proof.
intro n.
induction n as [ | n IH_n ].
(* n = 0 *)
left. reflexivity.
(* n <> 0 *)
right. my_destruct IH_n as
[ H_n_zero
| [
| n' k H_half H_wondrous_k
| n' k H_triple_one H_wondrous_k ] ].
Admitted.
We can inspect one of the generated goals to see how the names are being used.
oneness < Show 4.
subgoal 4 is:
n : nat
n' : nat
k : nat
H_triple_one : 3 * n' + 1 = k
H_wondrous_k : wondrous k
============================
wondrous (S n')
Related
This is slightly different from simple implication, as shown in this toy example.
Inductive R : nat -> nat -> Prop :=
| Base1: R 0 1
| Base2: R 0 2
| Ind: forall n m,
R n m -> R (n+1) (m+1).
Given this definition, we have three provable statements: R 2 3, R 3 5, and (R 2 3) -> (R 3 5). What I'm looking for is some way to formulate the following: "there does not exist a derivation path (i.e. a sequence of inductive constructor applications) that starts at R 2 3 and ends at R 3 5.
Is there a way to do this in Coq?
Here is a suggestion for how you can define a derivation path. I don't know that this is the best way, but it's what I came up with.
Require Import List Lia.
Import ListNotations.
Inductive evidence :=
| B1 : evidence
| B2 : evidence
| Step : nat -> nat -> evidence.
Inductive R : nat -> nat -> list evidence -> Prop :=
| Base1 : R 0 1 [B1]
| Base2 : R 0 2 [B2]
| Ind : forall n m es, R n m es -> R (n+1) (m+1) (Step n m :: es).
Lemma R_B2 (n : nat) (es : list evidence) : R n (n + 2) es -> In B2 es.
Proof.
generalize dependent n.
induction es as [|e es' IHes'].
- now intros Rnn2nil; inversion Rnn2nil.
- intros n Rnn2.
case es' as [| e' es''].
+ inversion Rnn2.
* now left.
* now inversion H2.
+ inversion Rnn2.
right.
apply (IHes' n0).
now replace (n0 + 2) with m by lia.
Qed.
You can probably simplify this proof, and avoid lia if you want.
I have
1 subgoals, subgoal 1
n : nat
b : bool
m : nat
H1: P1
H2: P2
H3: P1
H4: P2
=========
some_goal
after I run the tactic auto_group_duplicates,
it will become
1 subgoals, subgoal 1
n, m : nat
b : bool
H1, H3: P1
H2, H4: P2
=========
some_goal
Is there a tactic like this one?
I don't think that there is a tactic like this. But you can always come up with something using Ltac.
From Coq Require Import Utf8.
Definition mark {A : Type} (x : A) := x.
Ltac bar :=
match goal with
| x : ?A, y : ?A |- _ =>
lazymatch A with
| context [ mark ] => fail
| _ =>
move y after x ;
change A with (mark A) in x
end
end.
Ltac remove_marks :=
repeat match goal with
| x : mark ?A |- _ =>
change (mark A) with A in x
end.
Ltac auto_group_duplicates :=
repeat bar ; remove_marks.
Lemma foo :
∀ (n : nat) (b : bool) (m : nat) (c : bool),
n = m →
b = c →
n = m →
b = c →
n = m →
True.
Proof.
intros n b m c e1 e2 e3 e4 e5.
auto_group_duplicates.
auto_group_duplicates.
Here I have to apply the tactic twice because Ltac unifies A with mark A annoyingly.
You can manually use the move tactic (see https://coq.inria.fr/refman/proof-engine/tactics.html?highlight=top#coq:tacn.move-%E2%80%A6-after-%E2%80%A6) to rearrange hypothesis.
I doubt that there is a general automatic tactic like the one you are looking for, but one could probably cook up a fancy match-goal-based one to automate rearranging hypothesis using this tactic as a basic block, although this is out of my league.
I have two lists, one constructed directly by recursion and the other constructed using a map operation. I'm trying to show they are equal, and surprisingly I got stuck.
Require Import Coq.Lists.List.
Import ListNotations.
Fixpoint ls_zeroes n :=
match n with
| 0 => nil
| S n' => 0 :: ls_zeroes n'
end.
Fixpoint ls_ones n := map S (ls_zeroes n).
Fixpoint ls_ones' n :=
match n with
| 0 => nil
| S n' => 1 :: ls_ones' n'
end.
Goal forall n, ls_ones n = ls_ones' n.
Proof.
intros.
induction n.
- reflexivity.
- simpl. f_equal. (* ??? *)
Abort.
This is what the context looks like:
1 subgoal
n : nat
IHn : ls_ones n = ls_ones' n
______________________________________(1/1)
map S (ls_zeroes n) = ls_ones' n
I thought fold ls_ones would map S (ls_zeroes n) into ls_ones n since that's literally the definition of ls_ones but it does nothing. If I try to unfold ls_ones in IHn I get a nasty recursive expression instead of the verbatim definition of ls_ones.
What is the cleanest way to complete this proof?
Notice that when you define ls_one and unfold the definition you gets :
(fix ls_ones (n0 : nat) : list nat := map S (ls_zeroes n0)) n = ls_ones' n
The problem is that ls_one isn't a fixpoint. Indeed, it's doesn't make a recursion. Once coq automatically defines the point {struct n0} (in that case the n argument), your proof gets stuck because n is never destructed in P k -> P (k + 1), 'cause k is not destructed.
Using :
Definition ls_ones n := map S (ls_zeroes n).
The proof becomes trivial :
Goal forall n, ls_ones n = ls_ones' n.
Proof.
intros.
induction n.
trivial.
unfold ls_ones in *.
simpl.
rewrite IHn.
trivial.
Qed.
I thought fold ls_ones would map S (ls_zeroes n) into ls_ones n since that's literally the definition of ls_ones
Is it? You said Fixpoint ls_ones, not Definition. Just like any Fixpoint, this means that the given definition of ls_ones is transformed into a fix. There's no recursive structure in the definition given, so this is pointless, but you said to do it, so Coq does it. Issue Print ls_ones. to see the actual definition. The true solution is to make ls_ones a Definition.
If you don't fix that, Coq will only reduce a Fixpoint if the recursive argument(s) start with constructors. Then, in order to complete this proof, you need to destruct n to show those constructors:
Goal forall n, ls_ones n = ls_ones' n.
Proof.
intros.
induction n.
- reflexivity.
- simpl. f_equal. destruct n; assumption.
Qed.
Unfortunately, due to the value being fixed in your definitions you must use induction to do the proof:
From mathcomp Require Import all_ssreflect.
Set Implicit Arguments.
Unset Strict Implicit.
Unset Printing Implicit Defensive.
Fixpoint seq0 n :=
match n with
| 0 => nil
| S n' => 0 :: seq0 n'
end.
Fixpoint seq1 n :=
match n with
| 0 => nil
| S n' => 1 :: seq1 n'
end.
Lemma eq_F n : seq1 n = [seq n.+1 | n <- seq0 n].
Proof. by elim: n => //= n ->. Qed.
There is not a lot to proof tho. I'd recommend tho using the more general nseq count elem function instead of definition your own duplicate structures, then the proof follows pretty quickly from the general lemma about map:
Lemma eq_G n : nseq n 1 = [seq n.+1 | n <- nseq n 0].
Proof. by rewrite map_nseq. Qed.
I have a type defined as
Inductive bits : nat -> Set :=
| bitsNil : bits 0
| bitsCons : forall {l}, bool -> bits l -> bits (S l).
and I'm trying to prove:
Lemma emptyIsAlwaysNil : forall {a: bits 0}, a = bitsNil.
After intros, I've tried constructor 1, case a, intuition, to no avail. case a seems like the closest, but it gets an error:
Abstracting over the terms "0" and "a" leads to a term
fun (n : nat) (a0 : bits n) => a0 = bitsNil
which is ill-typed.
Reason is: Illegal application:
The term "#eq" of type "forall A : Type, A -> A -> Prop"
cannot be applied to the terms
"bits n" : "Set"
"a0" : "bits n"
"bitsNil" : "bits 0"
The 3rd term has type "bits 0" which should be coercible to
"bits n".
It sounds like it can't determine whether a bit-vector of an arbitrary length is equal to one of zero-length, because they're different at the type level. Is that correct?
Yes, you're basically correct: specifically, what isn't type checking is Coq's attempt to construct a match on a:bits 0 (which is what case does): the bitsCons case has an ill-typed conclusion.
Here's an axiom-free proof. The key idea is to manually generalize the statement to any n = 0 (I couldn't figure out how to do this with tactics; they all trip up on the dependency). The equality proof then makes the conclusion type check regardless of what n is, and we can dismiss the bitsCons case because we'll have n = S n'. In the more difficult bitsNil case, we make use of eq_rect_eq_dec, which is a consequence of Axiom K but is provable when the type index (nat, in this case) has decidable equality. See the Coq standard library documentation for some other things you can do without axioms with decidable equality.
Require PeanoNat.
Require Import Eqdep_dec.
Import EqNotations.
Inductive bits : nat -> Set :=
| bitsNil : bits 0
| bitsCons : forall {l}, bool -> bits l -> bits (S l).
Lemma emptyIsAlwaysNil_general :
forall n (H: n = 0) {a: bits n},
rew [bits] H in a = bitsNil.
Proof.
intros.
induction a; simpl.
(* bitsNil *)
rewrite <- eq_rect_eq_dec; auto.
apply PeanoNat.Nat.eq_dec.
(* bitsCons - derive a contradiction *)
exfalso; discriminate H.
Qed.
Lemma emptyIsAlwaysNil : forall {a: bits 0},
a = bitsNil.
Proof.
intros.
change a with (rew [bits] eq_refl in a).
apply emptyIsAlwaysNil_general.
Qed.
You don't need the rew H in x notation from EqNotations (it just wraps eq_rect, the equality recursion principle), but I find it makes things much more readable.
However, you can prove this theorem more simply if you're willing to use an axiom, specifically JMeq_eq (see CPDT's equality chapter for more details), since then you can use dependent induction or dependent destruction:
Require Import Program.Equality.
Inductive bits : nat -> Set :=
| bitsNil : bits 0
| bitsCons : forall {l}, bool -> bits l -> bits (S l).
Lemma emptyIsAlwaysNil :
forall {a: bits 0}, a = bitsNil.
Proof.
intros.
dependent destruction a; reflexivity.
Qed.
Print Assumptions emptyIsAlwaysNil.
(* Axioms:
JMeq_eq : forall (A : Type) (x y : A), x ~= y -> x = y *)
Here is a simple proof (borrowed from this Coq Club thread):
Definition emptyIsAlwaysNil {a: bits 0} : a = bitsNil :=
match a with bitsNil => eq_refl end.
Opaque emptyIsAlwaysNil.
Here is what Coq builds under the hood:
Print emptyIsAlwaysNil.
emptyIsAlwaysNil =
fun a : bits 0 =>
match
a as a0 in (bits n)
return
(match n as x return (bits x -> Type) with
| 0 => fun a1 : bits 0 => a1 = bitsNil
| S n0 => fun _ : bits (S n0) => IDProp
end a0)
with
| bitsNil => eq_refl
| bitsCons _ _ => idProp
end
: forall a : bits 0, a = bitsNil
Here are the inductive & computational definitions of evenness of natural numbers.
Inductive ev : nat -> Prop :=
| ev_0 : ev O
| ev_SS : forall n:nat, ev n -> ev (S (S n)).
Definition even (n:nat) : Prop :=
evenb n = true.
And the proof that one implies the other.
Theorem ev__even : forall n,
ev n -> even n.
intros n E.
induction E as [ | n' E' ].
reflexivity. apply IHE'. Qed.
I didn't think much of this proof at first, but on a closer look I find something troubling. The problem is that after the reflexivity step, I expect to see the context
1 subgoal
n' : nat
E : ev (S (S n'))
E' : ev n'
IHE' : ev n' -> even n'
====================================================================== (1/1)
even (S (S n'))
But what I actually get instead is
1 subgoal
n' : nat
E' : ev n'
IHE' : even n'
====================================================================== (1/1)
even (S (S n'))
Although the theorem is still provable as is, it is disturbing to see hypotheses mysteriously disappear. I'd like to know how to get the context I initially expected. From web searches I understand that this is a general problem with induction over constructed terms in Coq. One proposed solution on SO suggests using the remember tactic on hypotheses to be kept. But when I try that in this proof,
Theorem ev__even : forall n,
ev n -> even n.
intros n E.
remember E.
induction E as [ | n' E' ].
I get the following error message at the induction step.
Error: Abstracting over the term "n" leads to a term
"fun n : nat => forall e : ev n, e = E -> even n" which is ill-typed.
Which I do not really understand. I think the problem is that E has a free variable, but in that case I would be stuck, since there is no way to introduce E without also introducing n. (generalize dependent n would generalize E with it)
Is there any way to obtain the initially expected context?
To be useful, the induction tactic tries to generalize over all variables that depend on the thing you are doing induction on, and things that depend on the indices of its type. In your case, this implies generalizing over n, the newly generated proof e : ev n, and the equality e = E. However, it does not generalize over E itself, because the induction principles that are automatically generated for propositions ignore the proof argument. Unfortunately, this means that that generalization will be ill-typed, and your intuition is correct: because E was not generalized with n, its type will mention a different number, which will make the equality e = E ill-typed.
I don't understand what the induction tactic is doing here.
Whenever I don't understand what a tactic is doing, I try to just write the proof term myself.
If you invoke the induction principle by hand, you can keep the original hypothesis:
Theorem ev__even : forall n, ev n -> even n.
intros n E.
refine (ev_ind even _ _ n E).
- reflexivity.
- intros n' E' IH.
apply IH.
Qed.
This is what the context looks like in the second case of the induction:
n : nat
E : ev n
n' : nat
E' : ev n'
IH : even n'
============================
even (S (S n'))
Assuming
Fixpoint evenb (n:nat) : bool :=
match n with
| O => true
| S O => false
| S (S n') => evenb n'
end.