为什么嵌套归纳策略也将归纳假设嵌套在lambda下?

时间:2019-04-09 15:17:33

标签: coq

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.

以上内容摘自软件基金会第二章。

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

我对IHm应该在这里感到困惑。以我的理解,Coq策略是在某些功能程序的后台进行编译的,但是我真的不确定这里发生了什么。我很确定这不是我想要的。

我想做的事情类似于下面的Idris程序。

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

更具体地说,我需要使用对prf1的递归调用获得的prf2prf3mult_comm。在Coq中,这两个证明都卡在了lambda中,我不确定这是怎么发生的。我发现Coq的induction策略没有按照我认为应该做的做。

除了上面的解释之外,让我问一个问题,Coq是否比Software Foundations有更多入门材料,以防万一我因某种战术而再次陷入这种困境?请注意,因为我已经在线找到了解决方案,所以我知道如何在Coq中解决该问题。

我曾在2016年尝试将SF书作为依赖类型编程的介绍而失败,但是现在有了事后的见识,我发现Little Typer和Idris书在这方面要好得多。

1 个答案:

答案 0 :(得分:1)

调用induction策略时,Coq使用启发式方法来确定要归纳证明的谓词P : nat -> Prop。在第二次调用induction之前,证明状态如下:

  m, n : nat
  IHn : m * n = n * m
  ============================
  m * S n = m + m * n

发生的事情是Coq决定将前提IHn包括在归纳谓词中,据推测是

P m := m * n = n * m -> m * S n = m + m * n

这正是归纳假设中的内容。在这种情况下,您可能会认为Coq使用前提是很愚蠢的,但是在某些情况下,放弃它会导致无法证明的目标。例如,考虑以下证明尝试:

Lemma double_inj : forall n m, n + n = m + m -> n = m.
Proof.
  intros n m H.
  induction n as [|n IH].
  (* ... *)

如果在调用H之后丢弃了induction,则必须证明forall n m, n = m,显然不成立。

此示例是为什么在单个Coq证明中多次调用induction常常是个坏主意的原因之一。正如我们在Software Foundations的练习中所建议的那样,最好证明一个辅助引理,因为您可以明确地归纳谓词。对于此示例,还有其他选项。例如,您可以调用clear IHn来丢弃IHn前提,这将导致Coq到达正确的谓词。 Coq随附的抗反射证明语言具有另一种进行归纳的策略,称为elim,可让您在选择谓词时更加明确。

我同意您的最终意见,但我要补充一点,即不是软件基础的目的就是要介绍依赖类型的编程。尽管Coq支持这种范例,但是直接编写此类程序通常很麻烦,并且使用策略来证明有关简单键入程序的引理要容易得多。例如,您的mult_comm证明被Idris接受,因为它的终止检查器足够聪明,可以识别所有递归调用都在减少,即使相对于固定参数而言,递归调用没有减少(在第二个子句{{ 1}}减小,而在第三n中减小)。这在Coq中是不可能的,您必须将定义分成多个递归函数,每个递归函数一个,或者对成对的自然数使用有据可归的归纳法,在此示例中,这是过分的。

Adam Chlipala还有另一本名为CPDT的Coq教科书,您可能想看看。但是,我认为您也不会在其中找到对Coq策略的全面描述。像m一样,许多策略都依赖于启发式算法,并且难以详细解释。

最后,Matthieu Sozeau开发了一个名为Equations的软件包,该软件包使Coq中的依赖类型编程更加接近Idris或Agda。如果您发现这种证明方式更直观,则可以尝试使用它。