如果我有这样的谓词:
Inductive foo : nat -> nat -> Prop :=
| Foo : forall n, foo n n.
然后我可以通过诱导来证明一些虚假的引理:
Lemma foo_refl : forall n n',
foo n n' -> n = n'.
Proof.
intros.
induction H.
reflexivity.
Qed.
但是,对于带有产品类型参数的谓词:
Inductive bar : (nat * nat) -> (nat * nat) -> Prop :=
| Bar : forall n m, bar (n, m) (n, m).
几乎相同的引理的类似证据被卡住了,因为关于变量的所有假设都消失了:
Lemma bar_refl : forall n n' m m',
bar (n, m) (n', m') -> n = n'.
Proof.
intros.
induction H.
(* :( *)
为什么会这样?如果我将induction
替换为inversion
,则其行为符合预期。
引理仍然可以用induction
证明,但需要一些解决方法:
Lemma bar_refl : forall n n' m m',
bar (n, m) (n', m') -> n = n'.
Proof.
intros.
remember (n, m) as nm.
remember (n', m') as n'm'.
induction H.
inversion Heqnm. inversion Heqn'm'. repeat subst.
reflexivity.
Qed.
不幸的是,这种方式证据变得完全混乱,并且无法遵循更复杂的谓词。
一个明显的解决方案是像这样声明bar
:
Inductive bar' : nat -> nat -> nat -> nat -> Prop :=
| Bar' : forall n m, bar' n m n m.
这解决了所有问题。然而,就我的目的而言,我发现之前的(“tupled”)方法更为优雅。有没有办法保持谓词不变,仍然能够进行可管理的归纳证明?问题甚至来自哪里?
答案 0 :(得分:2)
问题在于归纳只能用于变量,而不能用于构造术语。这就是为什么你应该首先证明像
这样的东西Lemma bar_refl : forall p q, bar p q -> fst p = fst q.
由now induction 1.
轻易证明你的引理。
如果您不希望中间引理有名称,那么您的解决方案是正确的:您需要帮助Coq remember
来概括您的目标,然后您将能够证明它
我不记得这个限制究竟来自哪里,但我记得有些事情让一些统一问题变得不可判断。
答案 1 :(得分:1)
通常在这种情况下,可以对其中一个子词进行归纳。
在你的情况下,你的引理可以通过n
上的归纳来证明,
Lemma bar_refl : forall n n' m m', bar (n, m) (n', m') -> n = n'.
Proof. induction n; intros; inversion H; auto. Qed.
答案 2 :(得分:1)
...关于变量的所有假设都消失了......为什么会发生这种情况?如果我将
induction
替换为inversion
,则其行为符合预期。
在这篇博文中完美地描述了发生这种情况的原因: Dependent Case Analysis in Coq without Axioms 作者:James Wilcox。让我引用这个案例中最相关的部分:
当Coq执行案例分析时,它首先抽象所有索引。在谓词上使用
destruct
时,您可能已经将此清单视为信息丢失(例如,尝试破坏even 3
:它只是删除了假设!),或者在对带有具体索引的谓词进行归纳时(尝试通过对假设的归纳证明forall n, even (2*n+1) -> False
(而不是nat
) - 你会被卡住!)。 Coq基本上忘记了指数的具体价值。当试图引入这样的假设时,一种解决方案是用新变量替换每个具体索引以及强制变量等于正确具体值的约束。destruct
做了类似的事情:当给定具有具体索引值的某种归纳类型的术语时,它首先用新变量替换具体值。它没有添加等式约束(但inversion
确实如此)。这里的错误是关于抽象索引。你不能用任意变量替换具体值,并希望事情仍然是类型检查。它只是一种启发式方式。
举一个具体的例子,当使用destruct H.
时,基本上就像这样进行模式匹配:
Lemma bar_refl : forall n n' m m',
bar (n, m) (n', m') -> n = n'.
Proof.
intros n n' m m' H.
refine (match H with
| Bar a b => _
end).
具有以下证明状态:
n, n', m, m' : nat
H : bar (n, m) (n', m')
a, b : nat
============================
n = n'
要获得几乎确切的证明状态,我们应该使用H
命令从上下文中删除clear H.
:refine (...); clear H.
。这种相当原始的模式匹配并不能证明我们的目标。
Coq抽象出(n, m)
和(n',m')
,将其替换为p
和p'
对,p = (a, b)
和p' = (a, b)
。不幸的是,我们的目标是n = n'
形式,其中既没有(n,m)
也没有(n',m')
- 这就是为什么Coq没有用{{1}来改变目标的原因}}
但有一种方法可以告诉Coq这样做。我不知道如何使用战术完全,所以我将展示一个证明术语。这看起来有点类似于@Vinz的解决方案,但请注意我并没有改变引理的陈述:
a = a
这一次,我们为Coq添加了更多注释,以了解 Undo. (* to undo the previous pattern-matching *)
refine (match H in (bar p p') return fst p = fst p' with
| Bar a b => _
end).
类型的组件与目标之间的关联 - 我们明确命名为H
和p
对,因为我们告诉Coq将我们的目标视为p'
,它会用fst p = fst p'
替换目标中的p
和p'
。我们的证明状态现在看起来像这样:
(a,b)
而简单的n, n', m, m' : nat
H : bar (n, m) (n', m')
a, b : nat
============================
fst (a, b) = fst (a, b)
能够完成证明。
我认为现在应该清楚为什么reflexivity
在下面的引理中工作正常(不要看下面的答案,先试着弄清楚):
destruct
答案:因为目标包含假设类型中存在的相同对,因此Coq用适当的变量替换它们,这将防止信息丢失。
答案 3 :(得分:0)
另一种方式......
Lemma bar_refl n n' m m' : bar (n, m) (n', m') -> n = n'.
Proof.
change (n = n') with (fst (n,m) = fst (n',m')).
generalize (n,m) (n',m').
intros ? ? [ ]; reflexivity.
Qed.