Coq带等式归纳的证明

4

我有一个已知值的列表,希望在其上进行归纳,并跟踪原始列表,并通过元素引用它。也就是说,我需要通过不同的i来引用l[i],而不仅仅是(a :: l)。

我试图制定归纳原理来实现这一点。以下是一个程序,其中所有不必要的定理都替换为Admitted,使用简化的示例。目标是使用countDown_nth证明allLE_countDown,并以方便的形式使用list_nth_rect。(该定理可以直接证明,无需使用任何定理。)

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.

所以,我有一个list_nth_rect函数,并且能够使用它和refine证明定理。但是,我必须自己构建命题P。通常,你想使用归纳法。

这需要区分哪些元素是原始列表l,哪些是归纳的子列表s。因此,我可以使用remember。

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

这让我处于...
  a, b : nat
  s, l : list nat
  Heql : l = s
  Heqs : l = countDown a b
  ============================
  allLE s a = true

然而,我似乎无法像之前那样通过相等性测试。当我尝试时,出现以下情况
  induction l, s, Heql using list_nth_rect.

我遇到了错误

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".

那么,如何更改归纳原理以使其与归纳策略一起使用呢? 看起来它在外部变量和函数内部的变量之间混淆了。 但是,我没有办法谈论不在范围内的内部变量。 这非常奇怪,因为使用精炼调用没有问题。 我知道对于匹配,有as子句,但是我无法弄清楚如何在此应用。 或者,是否有一种方法可以使list_nth_rect使用P l l 0并仍然指示哪些变量对应于l和s?

3个回答

3

首先,您可以通过重复使用更基本的结果来更轻松地证明此结果。以下是一个基于ssreflect库定义的版本:

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.

在这里,iota n m是从n开始计数的包含m个元素的列表,all是你的allLE的通用版本。类似的函数和结果存在于标准库中。
回到你最初的问题,确实有时候我们需要在归纳列表的同时记住我们开始的整个列表。我不知道是否有一种方法可以使用标准归纳策略来获得你想要的结果;我甚至不知道它有一个多参数变体。当我想要使用这种策略证明P l时,我通常按照以下步骤进行:
1. 找到一个谓词Q:nat -> Prop,使得Q(length l)意味着P l。通常,Q n的形式为n <= length l -> R(take n l)(drop n l),其中R:list A -> list A -> Prop。 2. 对所有n进行归纳证明Q n

这个问题是作为一个简单的例子提出来的。是的,我可以很容易地证明它。但是,关键是能够使用countDown_nth。我对ssreflect不是很熟悉,但在https://x80.org/rhino-coq/上找到了一些信息。看起来你需要将它转换成单个元素形式,然后才能讨论那个元素。你使用iota的范围来建立定理。所以,这并没有直接回答我的问题。 - scubed
你说得对,这并没有完全回答你的问题;我必须承认我不知道如何将你的归纳原理表达成可以与“归纳”策略一起使用的形式。但是我很难看到一个好的用例,不能轻松地使用其他技术来处理它。是否有其他问题,你认为使用我最后提出的策略会很尴尬? - Arthur Azevedo De Amorim

3

我不确定这是否回答了你的问题,但是 induction 似乎接受 with 子句。因此,你可以写以下内容。

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).

但是相对于"refine"版本,这种方法的好处非常有限,因为需要手动指定谓词。

现在,我将介绍如何使用标准库中的对象证明这样的结果。

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.

编辑: 你可以陈述一个辅助引理来做出更简洁的证明。

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.

这非常接近。我知道归纳法有关系,但实际上没有测试过。很有趣的是,通过指定P,它也可以工作。我仍然困惑为什么在生成P时它不起作用。有没有办法告诉它生成哪个P?使用In_nth似乎解决了我的原始问题。它告诉你元素是否在范围内,并且是一个元素,因此允许你使用countDown_nth。 - scubed
问题似乎是Coq没有尝试使用其第三个参数不使用谓词。如果您尝试使用induction l, s, Heql using list_nth_rect with (P:=fun s l _ => _),则可以解决该问题(但会留下一个名为P的空洞,使目标变得不可读)。 - eponier
关于我的方法,你可以陈述一个定理总结 Forall_forallIn_nth,请看我的编辑。 - eponier

2
问题在于,无论好坏,归纳似乎假定其参数是独立的。那么解决方法是让归纳自动从Heql中推断出l和s:
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.

我不得不稍微改变list_nth_rect的类型;希望我没有让它失真。


就是这样!(是的,定理仍然成立,只是list_nth_rect_aux被相应地修改了。)问题在于我单独量化了l,在每个步骤中它似乎是一个不同的值。因此,它无法知道该值在整个过程中没有改变。所以,通过将l移到顶部并不创建新的l,它知道l实际上是相同的,因此归纳起来是有效的。 - scubed
请注意,即使s在每个步骤中都会改变,您也必须将s移到顶部。 - Jason Gross
如果将s移到顶部,则使其更容易工作,但这并非必要。如果您使用induction Heql using (fun P Pnil Pcons => list_nth_rect (l := l) P Pnil Pcons (s := s)),则在底部得到相同的结果。我不确定为什么会这样。 - scubed
这是因为必须使用相关值或evars填充sl,才能使此调用的“归纳”工作。 - Jason Gross

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接