I am a beginner Coq user.
While it is satisfying that the computer verified the proof for me, proofs that satisfy Coq are notoriously difficult to read for humans. Here is a simple example, imagine you don't see any of the comments:
Theorem add_comm : forall n m : nat,
n + m = m + n.
Proof.
intros n m.
induction m as [|m Hm].
- (* n + 0 = 0 + n *)
simpl. (* n + 0 = n *)
apply add_0_r. (* *)
- (* n + S m = S m + n *)
simpl. (* n + S m = S (m + n) *)
rewrite <- Hm. (* n + S m = S (n + m) *)
rewrite <- plus_n_Sm. (* S (n + m) = S (n + m) *)
reflexivity. (* *)
Qed.
I honestly cannot read the proof without the comments, even less so if I want to show it to audiences who don't know about Coq.
Writing these comments manually is tedious, and Coq generates them anyway. I wonder if Coq can simply automatically annotate my proof so to make it more readable?
It's not literally what you asked for, as it doesn't add text to the .v file itself (afaik), but Alectryon allows you to generate documentation where the goal state appears when you hover the mouse on top of a tactic. See here for several examples.
An alternative solution to annotating the proof is to replay it, by opening the file and stepping through it to see the intermediate goals. I think even experts often do this to understand a proof, and many Coq demos also resort to this to go over proofs. Depending on your setting and goals, this might be simpler than setting Alectryon up.
Related
I am reading Logical Foundations from Software Foundations series and i saw the plus_id_example that is:
Theorem plus_id_example : forall n m:nat,
n = m ->
n + n = m + m.
Proof.
intros n m.
intros H.
rewrite H.
reflexivity. Qed.
I could understand the solution, so i tried to solve it using absurd, what i want to do is:
Lets consider by absurd, that n+n <> m+m, so we have 2n <> 2m, n <> m, which is a contradiction since we have n=m as our hypothesis.
How could i write this using Coq tactics?
You can use one of the many contraposition-based lemmas in Coq: you can see them by using, for instance, Search "contra". in Coq.
Using the ssreflect tactic language, a proof based on this idea can be obtained as follows (I'm sure there must be shorter proofs):
Theorem plus_id_example : forall n m:nat,
n = m ->
n + n = m + m.
Proof.
move=> n m.
apply: contra_eq.
have twice : forall p, p + p = p * 2.
move=> p.
by rewrite -iter_addn_0 /= addn0.
by rewrite !twice eqn_mul2r.
Qed.
Somewhere between 5 and 8 years ago (probably 6 or 7) I wrote a full Formalization of Bubble Sort in Coq. One of the earliest Lemmas proven was the one in the title, which I called "sub_succ_r" (or maybe it's the standard name?):
forall n m : nat, (n - (S m))%nat = pred(n - m)
Now, back then, this was the very simple proof for that Lemma:
intros n m.
induction n m using n_double_ind.
simpl.
auto.
apply sub_0_r.
Qed.
"sub_0_r" is the Lemma that asserts that
forall n : nat, (n - 0)%nat = n
Now, attentive readers who are also familiar with modern Coq might have already spotted the problem with the old proof: that second line.
induction n m
i.e. simultaneous induction on 2 terms, doesn't work any more, not even when specifying that one is using "n_double_ind".
I've been wracking my head over how to prove this using first induction over n and then induction over m, but I just can't figure it out.
Any help, great or small, would be much appreciated.
If you want to apply induction on two variables simultaneously, you need to use comma separator, or Coq recognizes f t as f applied to t, so when you write n m this actually means n is a function and you want to apply it to m. Instead, use comma like this:
induction n, m.
this generates four subgoals. Two of them can be proved using auto so you can ask Coq to use auto tactic on every subgoal generated by induction, using semicolon like this:
induction n, m; auto.
and the remaining subgoals are easy to prove using the lemma you mentioned and induction hypotheses. so the whole script is as follows:
Lemma sub_0_r : forall n : nat, (n - 0) = n.
Admitted.
Theorem sub_succ_r: forall n m : nat, (n - (S m)) = pred(n - m).
Proof.
induction n, m; auto.
- apply sub_0_r.
- apply IHn.
Qed.
Also note that using %nat is not necessary.
But as you saw we only used IHn which means that we didn't use the induction hypothesis for m, so we do not need to use induction on m, only a destruct tactic would do the job, a better proof is:
induction n; destruct m; auto.
- apply sub_0_r.
- apply IHn.
which is minimal and elegant.
Theorem mult_comm : forall m n : nat,
m * n = n * m.
Proof.
intros.
induction n.
- simpl. rewrite (mult_0_r m). reflexivity.
- simpl.
rewrite <- IHn.
induction m.
simpl. reflexivity.
simpl.
The above is from the Software Foundation's second chapter.
1 subgoal
m, n : nat
IHn : S m * n = n * S m
IHm : m * n = n * m -> m * S n = m + m * n
______________________________________(1/1)
S (n + m * S n) = S (m + (n + m * n))
I am really confused as to what IHm is supposed to be here. The way I understand it, Coq tactics get compiled under the hood to some functional program, but I am really not sure what is going on here. I am pretty sure that this is not I intended it to do.
What I wanted to do is something like the following Idris program.
add_comm : {a,b : Nat} -> a + b = b + a
add_assoc : {a,b,c : Nat} -> (a + b) + c = a + (b + c)
total
mult_comm : {m,n : Nat} -> (m * n) = n * m
mult_comm {m = Z} {n = Z} = Refl
mult_comm {m = Z} {n = (S k)} = mult_comm {m=Z} {n=k}
mult_comm {m = (S k)} {n = Z} = mult_comm {m=k} {n=Z}
mult_comm {m = (S k)} {n = (S j)} =
let prf1 = mult_comm {m=k} {n=S j}
prf2 = mult_comm {m=S k} {n=j}
prf3 = mult_comm {m=k} {n=j}
prf_add_comm = add_comm {a=k} {b=j}
prf_add_assoc = add_assoc {a=k} {b=j} {c=j*k}
prf_add_assoc' = add_assoc {a=j} {b=k} {c=j*k}
in
rewrite prf1 in
rewrite sym prf2 in
rewrite prf3 in
rewrite sym prf_add_assoc in
rewrite sym prf_add_assoc' in
rewrite (add_comm {a=k} {b=j}) in
Refl
More specifically, I need prf1, prf2 and prf3 which I get using recursive calls to mult_comm. In Coq the two of the proofs are stuck in a lambda and I am not sure how that happened. I see that Coq's induction tactic is not doing what I think it should be doing.
In addition to the explanation of the above, let me also ask is there more introductory material to Coq than Software Foundations just in case I get stuck like this again on some tactic? Note that I know how to solve this in Coq as I've found the solution online.
I've tried tackling the SF book unsuccessfully back in 2016 as an introduction to dependently typed programming and now with the benefit of hindsight, I see that Little Typer and the Idris book are much better in that regard.
When you call the induction tactic, Coq uses heuristics to determine the predicate P : nat -> Prop that you want to prove by induction. Before calling induction for the second time, the proof state looks like this:
m, n : nat
IHn : m * n = n * m
============================
m * S n = m + m * n
What happened is that Coq decided to include the premise IHn in the induction predicate, which was inferred to be
P m := m * n = n * m -> m * S n = m + m * n
which is exactly what you had in your induction hypothesis. In this case, you could argue that it was silly for Coq to use the premise, but there are cases where dropping it would result in an unprovable goal. For instance, consider the following proof attempt:
Lemma double_inj : forall n m, n + n = m + m -> n = m.
Proof.
intros n m H.
induction n as [|n IH].
(* ... *)
If H were dropped after calling induction, you would have to prove forall n m, n = m, which clearly does not hold.
This example is one of the reasons why it is often a bad idea to call induction multiple times in a single Coq proof. As we suggest in that exercise in Software Foundations, it is better to prove an auxiliary lemma, since you can be explicit about the induction predicate. For this example, there are other options as well. You could, for instance, call clear IHn to drop the IHn premise, which would lead Coq to the correct predicate. The ssreflect proof language, which now ships with Coq, has a different tactic for performing induction called elim, which allows you to be more explicit in the choice of the predicate.
I agree with your final comment, but I should add that it is not the goal of Software Foundations to be an introduction to dependently typed programming. Though Coq supports this paradigm, it is generally cumbersome to write such programs directly, and much easier to use tactics to prove lemmas about simply typed programs. For instance, your proof of mult_comm is accepted by Idris because its termination checker is smart enough to recognize all recursive calls as decreasing, even though they are not decreasing with respect to a fixed argument (in the second clause, n decreases, whereas in the third m does). This is not possible in Coq, and you must split the definition into multiple recursive functions, one for each argument, or use well-founded induction on pairs of natural numbers, which would be overkill for this example.
Adam Chlipala has another Coq textbook called CPDT that you might want to check out. However, I don't think you will find a comprehensive description of Coq's tactics there, either. Like induction, many tactics rely on heuristics and are hard to explain in detail.
On a final note, Matthieu Sozeau has developed a package called Equations that makes dependently typed programming in Coq much closer to Idris or Agda. If you find this style of proving more intuitive, you could try to use it.
When I use Function to define a non-structurally recursive function in Coq, the resulting object behaves strangely when a specific computation is asked. Indeed, instead of giving directly the result, the Eval compute in ... directive return a rather long (typically 170 000 lines) expression. It seems that Coq cannot evaluate everything, and therefore returns a simplified (but long) expression instead of just a value.
The problem seems to come from the way I prove the obligations generated by Function. First, I thought the problem came from the opaque terms I used, and I converted all the lemmas to transparent constants. By the way, is there a way to list the opaque terms appearing in a term ? Or any other way to turn opaque lemmas into transparent ones ?
I then remarked that the problem came more precisely from the proof that the order used is well-founded. But I got strange results.
For example, I define log2 on the natural numbers by repeatedly applying div2. Here is the definition:
Function log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| n => S (log2 (Nat.div2 n))
end.
I get two proof obligations. The first one checks that n respects the relation lt in the recursive calls and can be proved easily.
forall n n0 n1 : nat, n0 = S n1 -> n = S (S n1) -> Nat.div2 (S (S n1)) < S (S n1)
intros. apply Nat.lt_div2. apply le_n_S. apply le_0_n.
The second one checks that lt is a well-founded order. This is already proved in the standard library. The corresponding lemma is Coq.Arith.Wf_nat.lt_wf.
If I use this proof, the resulting function behaves normally. Eval compute in log24 10. returns 3.
But if I want to do the proof myself, I do not always get this behaviour. First, if I end the proof with Qed instead of Defined, the result of the computation (even on small numbers) is a complex expression and not a single number. So I use Defined and try to use only transparent lemmas.
Lemma lt_wf2 : well_founded lt.
Proof.
unfold well_founded. intros n.
apply (lemma1 n). clear n.
intros. constructor. apply H.
Defined.
Here, lemma1 is a proof of the well-founded induction on the natural numbers. Here again, I can use already existing lemmas, such as lt_wf_ind, lt_wf_rec, lt_wf_rec1 located in Coq.Arith.Wf_nat, or even well_founded_ind lt_wf. The first one does not work, it seems this is because it is opaque. The three others work.
I tried to prove it directly using the standard induction on the natural numbers, nat_ind. This gives:
Lemma lemma1 : forall n (P:nat -> Prop),
(forall n, (forall p, p < n -> P p) -> P n) -> P n.
Proof.
intros n P H. pose proof (nat_ind (fun n => forall p, p < n -> P p)).
simpl in H0. apply H0 with (n:=S n).
- intros. inversion H1.
- intros. inversion H2.
+ apply H. exact H1.
+ apply H1. assumption.
- apply le_n.
Defined.
With this proof (and some variants of it), log2 has the same strange behaviour. And this proof seems to use only transparent objects, so maybe the problem is not there.
How can I define a Function that returns understandable results on specific values ?
I've managed to pin-point the place that causes troubles: it's inversion H2. in lemma1. It turns out we don't need that case-analysis and intuition can finish the proof (it doesn't pattern-match on H2):
Lemma lemma1 : forall n (P:nat -> Prop),
(forall n, (forall p, p < n -> P p) -> P n) -> P n.
Proof.
intros n P H. pose proof (nat_ind (fun n => forall p, p < n -> P p)).
simpl in H0. apply H0 with (n:=S n).
- intros. inversion H1.
- intros. intuition.
- apply le_n.
Defined.
If we use lemma1 with this proof, the computation of log2 10 results in 3.
By the way, here is my version of lt_wf2 (it lets us compute as well):
Lemma lt_wf2 : well_founded lt.
Proof.
unfold well_founded; intros n.
induction n; constructor; intros k Hk.
- inversion Hk.
- constructor; intros m Hm.
apply IHn; omega.
(* OR: apply IHn, Nat.lt_le_trans with (m := k); auto with arith. *)
Defined.
I believe the
Using Coq's evaluation mechanisms in anger blog post by Xavier Leroy explains this kind of behavior.
it eliminates the proof of equality between the heads before recursing over the tails and finally deciding whether to produce a left or a right. This makes the left/right data part of the final result dependent on a proof term, which in general does not reduce!
In our case we eliminate the proof of inequality (inversion H2.) in the proof of lemma1 and the Function mechanism makes our computations depend on a proof term. Hence, the evaluator can't proceed when n > 1.
And the reason inversion H1. in the body of the lemma doesn't influence computations is that for n = 0 and n = 1, log2 n is defined within the match expression as base cases.
To illustrate this point, let me show an example when we can prevent evaluation of log2 n on any values n and n + 1 of our choice, where n > 1 and nowhere else!
Lemma lt_wf2' : well_founded lt.
Proof.
unfold well_founded; intros n.
induction n; constructor; intros k Hk.
- inversion Hk. (* n = 0 *)
- destruct n. intuition. (* n = 1 *)
destruct n. intuition. (* n = 2 *)
destruct n. intuition. (* n = 3 *)
destruct n. inversion Hk; intuition. (* n = 4 and n = 5 - won't evaluate *)
(* n > 5 *)
constructor; intros m Hm; apply IHn; omega.
Defined.
If you use this modified lemma in the definition of log2 you'll see that it computes everywhere except n = 4 and n = 5. Well, almost everywhere -- computations with large nats can result in stack overflow or segmentation fault, as Coq warns us:
Warning: Stack overflow or segmentation fault happens when working with
large numbers in nat (observed threshold may vary from 5000 to 70000
depending on your system limits and on the command executed).
And to make log2 work for n = 4 and n = 5 even for the above "flawed" proof, we could amend log2 like this
Function log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| 4 => 2
| 5 => 2
| n => S (log2 (Nat.div2 n))
end.
adding the necessary proofs at the end.
The "well-founded" proof must be transparent and can't rely on pattern-matching on proof objects because the Function mechanism actually uses the lt_wf lemma to compute the decreasing termination guard. If we look at the term produced by Eval (in a case where evaluation fails to produce a nat), we'll see something along these lines:
fix Ffix (x : nat) (x0 : Acc (fun x0 x1 : nat => S x0 <= x1) x) {struct x0}
It's easy to see that x0 : Prop, so it gets erased when extracting the functional program log2 into, say OCaml, but Coq's internal evaluation mechanism have to use it to ensure termination.
The reduction behavior of functions defined by well-founded recursion in Coq is generally not very good, even when you declare your proofs to be transparent. The reason for this is that arguments of well-foundedness usually need to be done with complicated proof terms. Since these proofs terms end up appearing in well-founded recursive definitions, "simplifying" your function will make all of those proof terms appear, as you noticed.
It is easier to rely on custom tactics and lemmas to reduce functions defined this way. First, I would recommend favoring Program Fixpoint over Function, because the latter is much older and (I think) less well maintained. Thus, you would end up with a definition like this:
Require Import Coq.Numbers.Natural.Peano.NPeano.
Require Import Coq.Program.Wf.
Require Import Coq.Program.Tactics.
Program Fixpoint log2 n {wf lt n} :=
match n with
| 0 => 0
| 1 => 0
| n => S (log2 (Nat.div2 n))
end.
Next Obligation.
admit.
Qed.
Now, you just need to use the program_simpl tactic to simplify calls to log2. Here's an example:
Lemma foo : log2 4 = 2.
Proof.
program_simpl.
Qed.
Just like the title says, I'm looking for a way to prove st X + st Y = st Y + (st X - 1) + 1 in Coq. I've been trying applying various combinations of plus_comm, plus_assoc and plus_permute but I haven't been able to make it go through. Any suggestions?
Here is the goal window:
3 subgoal
n : nat
m : nat
st : state
H : st Y + st X = n + m /\ beval st (BNot (BEq (AId X) (ANum 0))) = true
______________________________________(1/3)
st Y + 1 + (st X - 1) = n + m
For integers, either ring or omega should be able to solve such a goal. It can also be done manually. It helps to disable notations so that function names appear (in order use SearchAbout to find useful lemmas). The following is probably not the shortest possible proof, just the first I found:
Require Import ZArith.
Lemma simple: forall x y, (x + y)%Z = (y + (x - 1) + 1)%Z.
intros.
rewrite Z.add_sub_assoc.
replace ((y + x)%Z) with ((x + y)%Z).
Focus 2.
rewrite Z.add_comm.
reflexivity.
set (t := ((x + y)%Z)).
replace (1%Z) with (Z.succ 0).
Focus 2.
symmetry.
apply Z.one_succ.
rewrite Zminus_succ_r.
rewrite Z.add_succ_r.
rewrite <- Zminus_0_l_reverse.
rewrite <- Zplus_0_r_reverse.
rewrite Z.succ_pred.
reflexivity.
Qed.
For those looking to use omega as a quick-fix, here's one way to get the goal into a form where it can be applied:
inversion H.
inversion H1.
rewrite negb_true_iff in H3.
apply beq_nat_false in H3.
omega.
For why omega works after we do this and not when the goal was in the original state, here is a great answer by Github user jaewooklee93:
"You don't need to think about plus_comm or similar lemmas here, because omega can solve those easy problems. Your goals are almost trivial, but the reason why omega hesistates to clear the goals is just because the minus between nat is not the same as the one we already know; 2-5=0, since there is no notion of negative in nat. So if you don't provide the fact that st X is greater than zero, omega cannot clear the goal for you. But you already have that condition in H1. Therefore, the only thing you should do is to simplify H1 and apply lemmas to H1 to make it st X<>0 .Then omega can properly work."