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
的递归调用获得的prf2
,prf3
和mult_comm
。在Coq中,这两个证明都卡在了lambda中,我不确定这是怎么发生的。我发现Coq的induction
策略没有按照我认为应该做的做。
除了上面的解释之外,让我问一个问题,Coq是否比Software Foundations有更多入门材料,以防万一我因某种战术而再次陷入这种困境?请注意,因为我已经在线找到了解决方案,所以我知道如何在Coq中解决该问题。
我曾在2016年尝试将SF书作为依赖类型编程的介绍而失败,但是现在有了事后的见识,我发现Little Typer和Idris书在这方面要好得多。
答案 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。如果您发现这种证明方式更直观,则可以尝试使用它。