4

I have a list with a known value and want to induct on it, keeping track of what the original list was, and referring to it by element. That is, I need to refer to it by l[i] with varying i instead of just having (a :: l).

I tried to make an induction principle to allow me to do that. Here is a program with all of the unnecessary Theorems replaced with Admitted, using a simplified example. The objective is to prove allLE_countDown using countDown_nth, and have list_nth_rect in a convenient form. (The theorem is easy to prove directly without any of those.)

Require Import Arith.
Require Import List.

Definition countDown1 := fix f a i := match i with
| 0 => nil
| S i0 => (a + i0) :: f a i0
end.

(* countDown from a number to another, excluding greatest. *)
Definition countDown a b := countDown1 b (a - b).

Theorem countDown_nth a b i d (boundi : i < length (countDown a b))
    : nth i (countDown a b) d = a - i - 1.
Admitted.

Definition allLE := fix f l m := match l with
| nil => true
| a :: l0 => if Nat.leb a m then f l0 m else false
end.

Definition drop {A} := fix f (l : list A) n := match n with
| 0 => l
| S a => match l with
  | nil => nil
  | _ :: l2 => f l2 a
  end
end.

Theorem list_nth_rect_aux {A : Type} (P : list A -> list A -> nat -> Type)
    (Pnil : forall l, P l nil (length l))
    (Pcons : forall i s l d (boundi : i < length l), P l s (S i) -> P l ((nth i l d) :: s) i)
    l s i (size : length l = i + length s) (sub : s = drop l i) : P l s i.
Admitted.

Theorem list_nth_rect {A : Type} (P : list A -> list A -> nat -> Type)
    (Pnil : forall l, P l nil (length l))
    (Pcons : forall i s l d (boundi : i < length l), P l s (S i) -> P l ((nth i l d) :: s) i)
    l s (leqs : l = s): P l s 0.
Admitted.

Theorem allLE_countDown a b : allLE (countDown a b) a = true.
  remember (countDown a b) as l.
  refine (list_nth_rect (fun l s _ => l = countDown a b -> allLE s a = true) _ _ l l eq_refl Heql);
    intros; subst; [ apply eq_refl | ].
  rewrite countDown_nth; [ | apply boundi ].
  pose proof (Nat.le_sub_l a (i + 1)).
  rewrite Nat.sub_add_distr in H0.
  apply leb_correct in H0.
  simpl; rewrite H0; clear H0.
  apply (H eq_refl).
Qed.

So, I have list_nth_rect and was able to use it with refine to prove the theorem by referring to the nth element, as desired. However, I had to construct the Proposition P myself. Normally, you'd like to use induction.

This requires distinguishing which elements are the original list l vs. the sublist s that is inducted on. So, I can use remember.

Theorem allLE_countDown a b : allLE (countDown a b) a = true.
  remember (countDown a b) as s.
  remember s as l.
  rewrite Heql.

This puts me at

  a, b : nat
  s, l : list nat
  Heql : l = s
  Heqs : l = countDown a b
  ============================
  allLE s a = true

However, I can't seem to pass the equality as I just did above. When I try

  induction l, s, Heql using list_nth_rect.

I get the error

Error: Abstracting over the terms "l", "s" and "0" leads to a term
fun (l0 : list ?X133@{__:=a; __:=b; __:=s; __:=l; __:=Heql; __:=Heqs})
  (s0 : list ?X133@{__:=a; __:=b; __:=s; __:=l0; __:=Heql; __:=Heqs})
  (_ : nat) =>
(fun (l1 l2 : list nat) (_ : l1 = l2) =>
 l1 = countDown a b -> allLE l2 a = true) l0 s0 Heql
which is ill-typed.
Reason is: Illegal application: 
The term
 "fun (l l0 : list nat) (_ : l = l0) =>
  l = countDown a b -> allLE l0 a = true" of type
 "forall l l0 : list nat, l = l0 -> Prop"
cannot be applied to the terms
 "l0" : "list nat"
 "s0" : "list nat"
 "Heql" : "l = s"
The 3rd term has type "l = s" which should be coercible to 
"l0 = s0".

So, how can I change the induction principle such that it works with the induction tactic? It looks like it's getting confused between the outer variables and the ones inside the function. But, I don't have a way to talk about the inner variables that aren't in scope. It's very strange, since invoking it with refine works without issues. I know for match, there's as clauses, but I can't figure out how to apply that here. Or, is there a way to make list_nth_rect use P l l 0 and still indicate which variables correspond to l and s?

scubed
  • 307
  • 3
  • 10

3 Answers3

3

First, you can prove this result much more easily by reusing more basic ones. Here's a version based on definitions of the ssreflect library:

From mathcomp
Require Import ssreflect ssrfun ssrbool ssrnat eqtype seq.

Definition countDown n m := rev (iota m (n - m)).

Lemma allLE_countDown n m : all (fun k => k <= n) (countDown n m).
Proof.
rewrite /countDown all_rev; apply/allP=> k; rewrite mem_iota.
have [mn|/ltnW] := leqP m n.
  by rewrite subnKC //; case/andP => _; apply/leqW.
by rewrite -subn_eq0 => /eqP ->; rewrite addn0 ltnNge andbN.
Qed.

Here, iota n m is the list of m elements that counts starting from n, and all is a generic version of your allLE. Similar functions and results exist in the standard library.

Back to your original question, it is true that sometimes we need to induct on a list while remembering the entire list we started with. I don't know if there is a way to get what you want with the standard induction tactic; I didn't even know that it had a multi-argument variant. When I want to prove P l using this strategy, I usually proceed as follows:

  1. Find a predicate Q : nat -> Prop such that Q (length l) implies P l. Typically, Q n will have the form n <= length l -> R (take n l) (drop n l), where R : list A -> list A -> Prop.

  2. Prove Q n for all n by induction.

Arthur Azevedo De Amorim
  • 23,012
  • 3
  • 33
  • 39
  • The question was made as a simple example. Yes, I can easily prove it. But, the point is to be able to use countDown_nth. I'm not very familiar with ssreflect, but found https://x80.org/rhino-coq/ . It looks like you convert it to a single element form and then are able to talk about just that element. You used the range of iota to establish the theorem. So, this doesn't directly address my question. – scubed Oct 04 '17 at 03:49
  • You are right, this does not exactly address your question; I must admit I don't know how to put your induction principle in a form that can be used with the `induction` tactic. But I am having trouble seeing a good use case for it that cannot be handled easily with other techniques. Is there some other problem where you think it would be awkward to use a strategy like the one I proposed in the end? – Arthur Azevedo De Amorim Oct 04 '17 at 04:05
3

I do not know if this answers your question, but induction seems to accept with clauses. Thus, you can write the following.

Theorem allLE_countDown a b : allLE (countDown a b) a = true.
  remember (countDown a b) as s.
  remember s as l.
  rewrite Heql.
  induction l, s, Heql using list_nth_rect
    with (P:=fun l s _ => l = countDown a b -> allLE s a = true).

But the benefit is quite limited w.r.t. the refine version, since you need to specify manually the predicate.

Now, here is how I would have proved such a result using objects from the standard library.

Require Import List. Import ListNotations.
Require Import Omega.

Definition countDown1 := fix f a i := match i with
| 0 => nil
| S i0 => (a + i0) :: f a i0
end.

(* countDown from a number to another, excluding greatest. *)
Definition countDown a b := countDown1 b (a - b).

Theorem countDown1_nth a i k d (boundi : k < i) :
  nth k (countDown1 a i) d = a + i -k - 1.
Proof.
  revert k boundi.
  induction i; intros.
  - inversion boundi.
  - simpl. destruct k.
    + omega.
    + rewrite IHi; omega.
Qed.

Lemma countDown1_length a i : length (countDown1 a i) = i.
Proof.
  induction i.
  - reflexivity.
  - simpl. rewrite IHi. reflexivity.
Qed.

Theorem countDown_nth a b i d (boundi : i < length (countDown a b))
    : nth i (countDown a b) d = a - i - 1.
Proof.
  unfold countDown in *.
  rewrite countDown1_length in boundi.
  rewrite countDown1_nth.
  replace (b+(a-b)) with a by omega. reflexivity. assumption.
Qed.

Theorem allLE_countDown a b : Forall (ge a) (countDown a b).
Proof.
  apply Forall_forall. intros.
  apply In_nth with (d:=0) in H.
  destruct H as (n & H & H0).
  rewrite countDown_nth in H0 by assumption. omega.
Qed.

EDIT: You can state an helper lemma to make an even more concise proof.

Lemma Forall_nth : forall {A} (P:A->Prop) l,
    (forall d i, i < length l -> P (nth i l d)) ->
    Forall P l.
  Proof.
    intros. apply Forall_forall.
    intros. apply In_nth with (d:=x) in H0.
    destruct H0 as (n & H0 & H1).
    rewrite <- H1. apply H. assumption.
  Qed.

Theorem allLE_countDown a b : Forall (ge a) (countDown a b).
Proof.
  apply Forall_nth.
  intros. rewrite countDown_nth. omega. assumption.
Qed.
eponier
  • 3,062
  • 9
  • 20
  • This is very close. I knew induction had with, but didn't actually test it. It is very interesting that with specifying P, it also works. I'm still confused why it doesn't work when it generates P. Is there a way to tell which P it generates? Using In_nth does seem to address my original problem. It gives you that the element is in bounds, and is an element, thus allowing you to use countDown_nth. – scubed Oct 04 '17 at 03:53
  • The problem seems to be that Coq does not try a predicate not using its third argument. If you try `induction l, s, Heql using list_nth_rect with (P:=fun s l _ => _)`, it works (but leave a hole for the content of `P`, which makes the goal unreadable). – eponier Oct 04 '17 at 09:38
  • As for my approach, you can state a theorem summing up `Forall_forall` and `In_nth`, see my edit. – eponier Oct 04 '17 at 09:40
2

The issue is that, for better or for worse, induction seems to assume that its arguments are independent. The solution, then, is to let induction automatically infer l and s from Heql:

Theorem list_nth_rect {A : Type} {l s : list A} (P : list A -> list A -> nat -> Type)
        (Pnil : P l nil (length l))
        (Pcons : forall i s d (boundi : i < length l), P l s (S i) -> P l ((nth i l d) :: s) i)
        (leqs : l = s): P l s 0.
Admitted.

Theorem allLE_countDown a b : allLE (countDown a b) a = true.
  remember (countDown a b) as s.
  remember s as l.
  rewrite Heql.
  induction Heql using list_nth_rect;
    intros; subst; [ apply eq_refl | ].
  rewrite countDown_nth; [ | apply boundi ].
  pose proof (Nat.le_sub_l a (i + 1)).
  rewrite Nat.sub_add_distr in H.
  apply leb_correct in H.
  simpl; rewrite H; clear H.
  assumption.
Qed.

I had to change around the type of list_nth_rect a bit; I hope I haven't made it false.

Jason Gross
  • 5,928
  • 1
  • 26
  • 53
  • That was it! (And yes, the theorem still holds, with list_nth_rect_aux modified accordingly.) The issue was that I had l quantified separately, making it an apparently different value at each step. So, it had no way to know the value didn't change over the whole process. So, by moving l to the top and not making a new one, it knows that l is actually the same, and thus induction works. – scubed Oct 04 '17 at 06:00
  • Note that you also have to move `s` to the top, even though `s` does change at each step. – Jason Gross Oct 04 '17 at 15:34
  • It makes it work more easily if you move `s` to the top, but it's not essential. You get the same result with `{s}` at the bottom if you do `induction Heql using (fun P Pnil Pcons => list_nth_rect (l := l) P Pnil Pcons (s := s)).` . I'm not sure why. – scubed Oct 05 '17 at 03:50
  • It's because both `s` and `l` *must* be filled in with the relevant values, or with evars, in order for this call to `induction` to work. – Jason Gross Oct 06 '17 at 05:00