在尝试破坏依赖类型的术语时,我经常在Coq中遇到错误。我知道关于堆栈溢出有两个与此问题相关的问题,但是对于我自己的证据而言,这两个问题都不足够概括。
这是错误发生位置的简单示例。
我们定义了一个类型族t
:
Inductive t: nat -> Set :=
| t_S: forall (n: nat), t (S n).
我们现在将尝试证明此类型家庭的每个成员t (S n)
都有一个术语,即t_S n
。
Goal forall (n: nat) (p: t (S n)), p = t_S n.
我们从开始:
intros n p.
下一步,我要破坏p
:
destruct p.
…但这会遇到以下错误:
Abstracting over the terms "n0" and "p" leads to a term fun (n1 : nat) (p0 : t n1) => p0 = t_S n
which is ill-typed.
Reason is: Illegal application:
The term "@eq" of type "forall A : Type, A -> A -> Prop"
cannot be applied to the terms
"t n1" : "Set"
"p0" : "t n1"
"t_S n" : "t (S n)"
The 3rd term has type "t (S n)" which should be coercible to "t n1".
在我看来,它正在尝试将p
转换为t_S n1
,但是以某种方式未能调和n1
必须等于n
的事实,从而导致=
的相对两侧具有不匹配的类型。
为什么会发生这种情况?如何解决这个问题?
答案 0 :(得分:2)
这个事实的简单证明是
Goal forall (n: nat) (p: t (S n)), p = t_S n.
Proof.
intros n p.
refine (
match p with
| t_S n => _
end
).
reflexivity.
Qed.
要了解其工作原理,这将有助于了解Coq在此处构造的证明术语。
Goal forall (n: nat) (p: t (S n)), p = t_S n.
Proof.
intros n p.
refine (
match p with
| t_S n => _
end
).
reflexivity.
Show Proof.
(fun (n : nat) (p : t (S n)) =>
match
p as p0 in (t n0)
return
(match n0 as x return (t x -> Type) with
| 0 => fun _ : t 0 => IDProp
| S n1 => fun p1 : t (S n1) => p1 = t_S n1
end p0)
with
| t_S n0 => eq_refl
end)
因此,证明字词并不是p
上的简单匹配项。相反,Coq巧妙地概括了S n
中的p: t (S n)
,同时将目标类型更改为在S n
情况下仍然匹配的目标。
具体来说,上面的证明词使用类型
match (S n) as n' return (t n' -> Type) with
| 0 => fun p => IDProp (* Basically the same as `unit`; a singleton type *)
| S n' => fun p => p = t_S n'
end p
显然,这与p = t_S n
相同,但是它可以使S n
泛化。 n
的每个实例现在都具有S n
的形式,因此可以用某些n'
通用地替换它。这是如何用个别策略编写的。
Goal forall (n: nat) (p: t (S n)), p = t_S n.
Proof.
intro n.
change (
forall p: t (S n),
match (S n) as n' return (t n' -> Type) with
| 0 => fun p => Empty_set (* This can actually be any type. We may as well use the simplest possible type. *)
| S n' => fun p => p = t_S n'
end p
).
generalize (S n); clear n.
intros n p.
(* p: t n, not t (S n), so we can destruct it *)
destruct p.
reflexivity.
Qed.
那么为什么这一切都是必要的?归纳(在特殊情况下,大小写匹配)要求归纳类型中的任何索引都是通用的。通过查看t
:t_rect: forall (P: forall n: nat, t n -> Type), (forall n: nat, P (S n) (t_S n)) -> forall (n: nat) (x: t n), P n x
的归纳原理可以看出这一点。
使用归纳法时,我们需要为所有自然数定义P
。即使归纳的另一个假设forall n: nat, P (S n) (t_S n)
仅使用P (S n)
,它的值仍需为零。对于您拥有的目标,P (S n) p := (p = t_S n)
,但尚未为P
定义0
。更改目标的巧妙技巧是将P
扩展到0
,使其方式与S n
的定义一致。