Proof leaking in Coq extraction? - coq

In order to understand how general recursive Function definitions works, and how they comply with Coq's structural recursion constraint, I tried to reimplement it on the Peano natural numbers. I want to define recursive nat -> nat functions that can use any previous values, not just the predecessor. Here is what I did :
Definition nat_strong_induction_set
(* erased at extraction, type specification *)
(P : nat -> Set)
(* The strong induction step. To build the P n it can, but does not have to,
recursively query the construction of any previous P k's. *)
(ind_step : forall n : nat, (forall k : nat, (lt k n -> P k)) -> P n)
(n : nat)
: P n.
Proof.
(* Force the hypothesis of ind_step as a standard induction hypothesis *)
assert (forall m k : nat, lt k m -> P k) as partial_build.
{ induction m.
- intros k H0. destruct k; inversion H0.
- intros k H0. apply ind_step. intros k0 H1. apply IHm. apply (lt_transitive k0 k).
assumption. apply le_lt_equiv. assumption. }
apply (partial_build (S n) n). apply succ_lt.
Defined.
I used some custom lemmas on nats that I didn't paste here. It works, I managed to define the euclidean division div a b with it, which recursively uses div (a-b) b. The extraction is almost what I expected :
let nat_strong_induction_set ind_step n =
let m = S n in
let rec f n0 k =
match n0 with
| O -> assert false (* absurd case *)
| S n1 -> ind_step k (fun k0 _ -> f n1 k0)
in f m n
Except for the n0 parameter. We see that the only effect of this parameter is to stop the recursion at the S n-nth step. The extraction also mentions that this assert false should not happen. So why is it extracted ? This seems better
let nat_strong_induction_set ind_step n =
let rec f k = ind_step k (fun k0 _ -> f k0)
in f n
It looks like a glitch of Coq's structural recursion constraint, to ensure the termination of all recursions. The Coq definition of nat_strong_induction_set writes lt k n, so Coq knows only previous P k's will be queried. This makes a decreasing chain in the nats, which is forced to terminate in less than S n steps. This allows a structural recursive definition on an additional fuel parameter n0 starting at S n, it won't affect the result. So if it is only a part of the termination proof, why is it not erased by the extraction ?

Your match is not erased because your definition mixes two things: the termination argument, where the match is needed, and the computationally relevant recursive call, where it isn't.
To force erasure, you need to convince Coq that the match is computationally irrelevant. You can do so by making the termination argument -- that is, the induction on m -- produce the proof of a proposition instead of a function of type forall m k, lt k m -> P k. Luckily, the standard library provides an easy way of doing so, with the Fix combinator:
Require Import Coq.Arith.Wf_nat.
Definition nat_strong_induction_set
(P : nat -> Set)
(ind_step : forall n : nat, (forall k : nat, (lt k n -> P k)) -> P n)
(n : nat)
: P n :=
Fix lt_wf P ind_step n.
Here, lt_wf is a proof that lt is well-founded. When you extract this function, you get
let rec nat_strong_induction_set ind_step n =
ind_step n (fun y _ -> nat_strong_induction_set ind_step y)
which is exactly what you wanted.
(As an aside, note that you don't need well-founded recursion to define division -- check for instance how it is defined in the Mathematical Components library.)

Related

IndProp: ev_plus_plus

(** **** Exercise: 3 stars, standard, optional (ev_plus_plus)
This exercise just requires applying existing lemmas. No
induction or even case analysis is needed, though some of the
rewriting may be tedious. *)
Theorem ev_plus_plus : forall n m p,
even (n+m) -> even (n+p) -> even (m+p).
Proof.
intros n m p H1 H2.
Here is what I got:
1 subgoal (ID 89)
n, m, p : nat
H1 : even (n + m)
H2 : even (n + p)
============================
even (m + p)
I have proven the previous theorem:
Theorem ev_ev__ev : forall n m,
even (n+m) -> even n -> even m.
And wanted to apply it to H1, but
apply ev_ev__ev in H1.
gives an error:
Error: Unable to find an instance for the variable m.
Why can't it find "m" in the expression even (n + m)? How to fix?
Update
apply ev_ev__ev with (m:=m) in H1.
gives a very strange result:
2 subgoals (ID 90)
n, m, p : nat
H1 : even m
H2 : even (n + p)
============================
even (m + p)
subgoal 2 (ID 92) is:
even (n + m + m)
I thought that it will transform H1 to 2 hypothesis:
H11 : even n
H12 : even m
But instead it gave 2 subgoals, the second that we need to prove is more complicated than the initial one:
even (n + m + m)
What's happening here?
The statement forall n m, even (n+m) -> even n -> even m. does not mean "if we have that (n + m) is even then we have both that n is even and that m is even" (this is false, consider n = m = 1). Instead it means "if we have that (n+m) is even, and we have that n is even, then we have that m is even".
There is no way to get H11 : even n and H12 : even m just from H1 : even (n + m) without assuming a contradiction. I would suggest figuring out how to prove your theorem with pen and paper before trying to prove it in Coq.
Because Coq can't figure out what value it should give for m. You can apply the tactic eapply ev_ev__ev in H1. and see the goals
n, m, p : nat
H2 : even (n + p)
H1 : even ?m
============================
even (m + p)
subgoal 2 (ID 17) is:
even (n + m + ?m)
Coq has instantiated the m with a meta variable ?m, and you need to give a witness for this meta variable in the end to finish the proof.
Second approach is just apply the tactic with instantiating the value of m apply ev_ev__ev with (m := m) in H1.
You can see more on apply with tactics in software-foundations https://softwarefoundations.cis.upenn.edu/lf-current/Tactics.html
The thing that is happening is that Coq unifies H1 with the even n argument of ev_ev__ev instead of the even (n+m).
You can tell Coq exactly where you want H1 to go, and use _ wildcards for the places where you let Coq work out the details.
You probably wanted this the term ev_ev__ev n m H1 with type even n -> even m but your apply produced the term ev_ev__ev (n+m) m _ H1 which also left you with some more stuff to prove. To take a look at the proof context, do
Check ev_ev__ev (n+m) m _ H1.

Understanding the induction on evidence in coq

I am working on the theorem ev_ev__ev in IndProp.v of Software Foundations (Vol 1: Logical Foundations).
Theorem ev_ev__ev : forall n m,
even (n+m) -> even n -> even m.
Proof.
intros n m Enm En. induction En as [| n' Hn' IHn'].
- (* En: ev_0 *) simpl in Enm. apply Enm.
- (* En: ev_SS n' Hn': even n'
with IHn': even (n' + m) -> even m *)
apply IHn'. simpl in Enm. inversion Enm as [| n'm H]. apply H.
Qed.
where even is defined as:
Inductive even : nat -> Prop :=
| ev_0 : even 0
| ev_SS (n : nat) (H : even n) : even (S (S n)).
At the point of the second bullet -, the context as well as the goal is as follows:
m, n' : nat
Enm : even (S (S n') + m)
Hn' : even n'
IHn' : even (n' + m) -> even m
______________________________________(1/1)
even m
I understand how m, n', Enm, Hn' in the context are generated. However, how is IHn' generated?
Induction hypotheses are systematically created for premises of constructors that are in the same type family. So, you can look at each constructor independently.
Assume you have an inductive definition of a type that starts with:
Inductive arbitraryName : A -> B -> Prop :=
An induction principle called arbitraryName_ind will be created, which starts with a quantification over an arbitrary predicate usually called P with the same type
forall P : A -> B -> Prop,
Now, if you have a constructor of the form
arbitrary_constructor : forall x y, arbitraryName x y -> ...
The induction principle will have a sub-clause for this constructor that starts with the same quantifications over all variables in the constructor, the same hypothesis, plus an induction hypothesis for the premise that relies on arbitraryName.
forall x y, arbitraryName x y -> P x y -> ...
Finally, each constructor of the inductive definition has to finish with an application of the defined type family (in this case arbitraryName). The end of the clause for this constructor apply the function P to the same argument.
Let's go back to arbitrary_constructor and suppose it has the following full type:
arbitrary_constructor : forall x y, arbitraryName x y -> arbitraryName (g x y) (h x y)
In that case the clause in the induction principle is :
(forall x y, arbitraryName x y -> P x y -> P (g x y) (h x y))
In the case of even, there is a constructor ev_SS that has the following shape:
ev_SS : forall x, even x -> even (S (S x))
So the clause that is generated has the following shape:
(forall x, even x -> P x -> P (S (S x)))
The induction hypothesis IHn' corresponds exactly to this P in the clause.
The full induction principle has the following shape.
forall P : nat -> Prop, P 0 ->
(forall x, even x -> P x -> P (S (S x))) ->
forall n, even n -> P n
When you type induction En, this theorem is applied. The hypothesis even n, where n is universally quantified, is matched with the text of En in the goal at that moment. It turns out that the statement of that hypothesis is even n (the n here is fixed in the goal) so the universally quantified n is instantiated with the local n from the goal context. Then, the tactic tries to find all the hypotheses in the context where this n appears. In this case, there is Enm, so this hypothesis is used to define the P on which the induction principle will be instantiated. In a sense, what happens is that Enm is put back in the goal's conclusion, as if one had executed revert Enm.
We need P n to be the same thing as even (n + m) -> even m. The most natural solution is that P is equal to the function fun x => even (x + m) -> even m
So in the second case of the proof by induction, a new n' is introduced and P is applied to n' to give the contents of the induction hypothesis:
(even (n' + m) -> even m)
and P is applied to S (S n') to give the contents of the final goal.
even (S (S n') + m) -> even m
Now, at the time of calling the induction tactic, the hypothesis Enm was in the context, so the statement even (S (S n') + m), which is morally an offspring of Enm is put back in the context with the same name. Note that there was already a hypothesis named Enm in the other goal, but the statement was again different.
It is normal that you have a question on how this induction hypothesis was generated, because what happens actually involves several operations.

renaming part of hypothesis in Coq

After destructing n in my proof, I am stuck at the following:
1 subgoal
n : nat
X : Type
h : X
t : list X
n' : nat
E : n = S n'
H' : length t = n'
IHl : length t = n -> nth_error t n = None
______________________________________(1/1)
nth_error t n' = None
I want to rewrite using IHl, but that is not possible. How do I compose IHl and H' to make sense and prove this theorem?
I am just trying to elaborate on #Arthur answer.
I was able to reproduce your goal with the following script:
Require Import List.
Lemma toto (n : nat) (X : Type) (l : list nat) : length l = n -> nth_error l n = None.
Proof.
induction l as [ | h t IHl].
case_eq n.
simpl; auto.
simpl; discriminate.
case_eq n.
simpl; discriminate.
intros n' E.
simpl; intros E'; injection E'; clear E'; intros H'.
and I agree that this goal cannot be proved. Now, if you instead start your proof with the following text (the Proof and induction lines have to be replaced), it will be provable (I checked).
Proof.
revert n.
induction l as [ | h t IHl]; intros n.
The difference is that the induction hypothesis now has the following statement.
forall n : nat, length t = n -> nth_error t n = None
What happened? In the first (faulty) variant, you attempt to prove a statement for all lists whose length is equal to a precise n, because n is fixed before you start the proof by induction. In the second (correct) variant, you attempt to prove a statement for all lists l, and this statement accepts any n as long as length l = n.
In the first variant, n is fixed and the equality length l = n restricts l to be among those that have length precisely n. In the second case, l is chosen first, and n is not fixed, but the equality length l = n restricts n to follow the length of l.
This is called "loading the induction" because the statement forall n, length l = n -> nth_error l n = None is stronger (it is loaded) than the statement that you attempt to prove in the first variant (just for one specific n), but surprisingly it is easier to prove.
You cannot, because your induction hypothesis is not general enough. Here is a statement that should be easier to prove:
forall (X : Type) (t : list X), nth_error t (length t) = None

Attempting to use proof irrelevance without creating ill-typed terms

To illustrate the issue I am facing let us assume we have a predicate on nat:
Parameter pred : nat -> Prop
Let us assume further that we have a type which encapsulates data, as well as a proof that the encapsulated data satisfies a certain property. For example:
Inductive obj : Type :=
| c : forall (n:nat), pred n -> obj
.
Now we would like to regard two objects c n p and c m q to be the same objects as long as n = m, regardless of the proofs involved to build them. So let us introduce a proof irrelevance axiom:
Axiom irrel : forall (P:Prop) (p q:P), p = q.
Now given this axiom, it is expected that the equality c n p = c m q be provable for n = m :
Theorem obvious : forall (n m:nat) (p: pred n) (q:pred m),
n = m -> c n p = c m q.
Now I have been playing around with this for a while, and none of the typical 'rewrite' tactics can work as they create ill-typed terms. I am guessing the theorem should be true within Coq's type theory (given the proof irrelevance axiom) but probably involves some trick unknown to a beginner. Any suggestion is greatly appreciated.
TL;DR
Theorem obvious n m (p: pred n) (q: pred m) :
n = m -> c n p = c m q.
Proof.
intros ->.
rewrite (irrel _ p q).
reflexivity.
Qed.
Explanation
Let me show how one can use information containing in error messages to come up with a solution:
Theorem obvious n m (p: pred n) (q: pred m) :
n = m -> c n p = c m q.
Proof.
intros E.
Fail rewrite E.
At this point we get the following error message:
The command has indeed failed with message:
Abstracting over the term "n" leads to a term fun n0 : nat => c n0 p = c m q
which is ill-typed.
Reason is: Illegal application:
The term "c" of type "forall n : nat, pred n -> obj"
cannot be applied to the terms
"n0" : "nat"
"p" : "pred n"
The 2nd term has type "pred n" which should be coercible to "pred n0".
The rewrite tactic tried to build the proof term using eq_ind_r lemma. Let us look at its type:
eq_ind_r
: forall (A : Type) (x : A) (P : A -> Prop),
P x -> forall y : A, y = x -> P y
rewrite tries to build the following term:
#eq_ind_r _ m (fun x => c x p = c m q) (subgoal : c m p = c m q) n E.
which is ill-typed:
Fail Check #eq_ind_r _ m (fun x => c x p = c m q).
The term "p" has type "pred n" while it is expected to have type "pred x".
This means that the link between n and pred n has been lost at this point and we can restore it by saying explicitly that x and p must comply with each other by generalizing over p:
Check #eq_ind_r _ m (fun x => forall (p : pred x), c x p = c m q).
The above means we can proceed to finish the proof in the following manner:
revert p.
rewrite H; intros p.
rewrite (irrel _ p q).
reflexivity.
Qed.
The original version of the code uses intro-pattern intros -> to achieve the effect of the longer intros E; revert p; rewrite E; intros p. for this particular case.

Prove equality on Sigma-types

I have defined a Sygma-Type that looks like:
{ R : nat -> nat -> bool | Reflexive R }
I have two elements r1 r2 : { R : nat -> nat -> bool | Reflexive R } and I am to prove r1 = r2. How can I do that?
If you want to show such an equality, you need to (1) show that the underlying functions are equal (i.e., the R component of your sigma type), and (2) show that the corresponding proofs are equal. There are two problems, however.
The first one is that equality of functions is too weak in Coq. According to common mathematical practice, we expect two functions to be equal if they yield equal results for any inputs. This principle is known as functional extensionality:
Axiom functional_extensionality :
forall A (B : A -> Type)
(f g : forall a, B a),
(forall x, f x = g x) ->
f = g.
As natural as it sounds, however, this principle is not provable in Coq's logic! Roughly speaking, the only way two functions can be equal is if they can be converted to a syntactically equal terms according to the computation rules of the logic. For instance, we can show that fun n : nat => 0 + n and fun n : nat => n are equal because + is defined in Coq by pattern-matching on the first argument, and the first argument on the first term is 0.
Goal (fun n : nat => 0 + n) = (fun n : nat => n). reflexivity. Qed.
We could expect to show that fun n => n + 0 and fun n => n are equal by similar means. However, Coq does not accept this, because + cannot be simplified when the first argument is a variable.
The other problem is that the notion of equality on proofs is not very interesting as well. The only way one can show that two proofs are equal is, again, syntactic equality. Intuitively, however, one would like to argue by proof irrelevance, a principle that states that proofs of the same thing are always equal:
Axiom proof_irrelevance :
forall (P : Prop) (p q : P), p = q.
but, again, this principle is not provable in the logic. Fortunately, Coq's logic was designed to allow one to add these principles as axioms in a sound way. One then gets the following proof:
Axiom functional_extensionality :
forall A (B : A -> Type)
(f g : forall a, B a),
(forall a, f a = g a) ->
f = g.
Axiom proof_irrelevance :
forall (P : Prop) (p q : P), p = q.
Lemma l (r1 r2 : { R : nat -> nat -> bool |
forall n, R n n = true }) :
(forall n1 n2, proj1_sig r1 n1 n2 = proj1_sig r2 n1 n2) ->
r1 = r2.
Proof.
destruct r1 as [r1 H1], r2 as [r2 H2].
simpl.
intros H.
assert (H' : r1 = r2).
{ apply functional_extensionality.
intros n1.
apply functional_extensionality.
intros n2.
apply H. }
subst r2.
rename r1 into r.
f_equal.
apply proof_irrelevance.
Qed.
Even though axioms can be useful, one might like to avoid them. In this case, it is actually possible to prove this lemma just with functional extensionality, but you do need at least that. If you want to avoid using axioms, and r1 and r2 are not equal up to computation, you'll have to use a difference equivalence relation on your type, and do your formalization using that relation instead, e.g.
Definition rel_equiv (r1 r2 : { R : nat -> nat -> bool | forall n, R n n = true }) : Prop :=
forall n1 n2, proj1_sig r1 n1 n2 = proj2_sig r2 n1 n2.
The standard library has good support for rewriting with equivalence relations; cf. for instance this.