我正在努力学习Coq,但我发现很难从我在软件基础和依赖类型的认证编程中读到的内容实现自己的飞跃用例。
特别是,我想我会尝试在列表上创建nth
函数的验证版本。我设法写了这个:
Require Import Arith.
Require Import List.
Import ListNotations.
Lemma zltz: 0 < 0 -> False.
Proof.
intros. contradict H. apply Lt.lt_irrefl.
Qed.
Lemma nltz: forall n: nat, n < 0 -> False.
Proof.
intros. contradict H. apply Lt.lt_n_0.
Qed.
Lemma predecessor_proof: forall {X: Type} (n: nat) (x: X) (xs: list X),
S n < length (x::xs) -> n < length xs.
Proof.
intros. simpl in H. apply Lt.lt_S_n. assumption.
Qed.
Fixpoint safe_nth {X: Type} (n: nat) (xs: list X): n < length xs -> X :=
match n, xs with
| 0, [] => fun pf: 0 < length [] => match zltz pf with end
| S n', [] => fun pf: S n' < length [] => match nltz (S n') pf with end
| 0, x::_ => fun _ => x
| S n', x::xs' => fun pf: S n' < length (x::xs') => safe_nth n' xs' (predecessor_proof n' x xs' pf)
end.
这有效,但它提出了两个问题:
{ | }
类型的用例吗?我试过了:
Require Import NPeano.
Eval compute in if ltb 2 (length [1; 2; 3]) then safe_nth 2 [1; 2; 3] ??? else 0.
但是,当我找出为???
部分写的内容之前,这当然不会奏效。我尝试将(2 < length [1; 2; 3])
放在那里但是类型Prop
而不是2 < length [1; 2; 3]
类型。我可以编写并证明该特定类型的引理,并且有效。但是什么是一般解决方案?
答案 0 :(得分:7)
我不认为就做这类事情的最佳方式达成了共识。
我认为Coq的开发通常倾向于使用索引归纳类型来编写这样的代码。这是Coq分布中vector library所遵循的解决方案。在那里,您将为向量定义索引归纳类型,为有界整数定义另一个归属类型(分别在标准库中称为Vector.t
和Fin.t
)。某些函数,例如nth
,在这种风格下编写起来要简单得多,因为矢量和索引上的模式匹配最终会在摆脱相互矛盾的情况并进行递归调用时为你做一些推理,例如。缺点是Coq中的依赖模式匹配不是很直观,有时您必须以奇怪的方式编写函数才能使它们起作用。这种方法的另一个问题是需要重新定义许多在列表上工作的函数来处理向量。
另一种解决方案是将有界整数定义为nat
的从属对,并证明该索引是有界的,这实际上就是您提到{ | }
类型时的目的。这是ssreflect库遵循的方法(例如,查看ordinal
类型)。要定义一个安全的nth
函数,他们所做的是定义一个简单的版本,当索引超出范围时使用默认元素返回,并使用n < length l
提供该默认元素的证明(查看ssreflect的tuple库,例如,他们定义长度索引列表,并查看它们如何定义tnth
)。优点是更容易将更多信息类型和功能与更简单的变体相关联。缺点是有些东西变得难以直接表达:例如,你不能直接在ssreflect元组上进行模式匹配。
值得注意的另一点是,通常使用布尔属性而不是归纳定义的更容易,因为计算和简化消除了对某些引理的需要。因此,当使用布尔版<
时,Coq在0 < 0 = true
和false = true
的证明之间,或S n < length (x :: l) = true
的证明之间没有区别。 n < length l = true
的证明,这意味着您可以直接在nth
的定义中使用这些证明,而无需使用辅助引理按摩它们。遗憾的是,Coq标准库倾向于优先于布尔计算的归纳定义类型,在许多情况下它们是无用的,例如用于定义<
。另一方面,ssreflect库更多地使用布尔计算来定义属性,使其更适合这种编程风格。
答案 1 :(得分:3)
zltz
与nltz 0
具有相同的类型。
Check zltz.
Check nltz 0.
要在其他功能中使用2
和[1; 2; 3]
功能,您可以使用lt_dec
。
Eval compute in match lt_dec 2 (length [1; 2; 3]) with
| left pf => safe_nth 2 [1; 2; 3] pf
| right _ => 0
end.
如果您提取lt_dec
,则在删除校样后,您会发现它与ltb
非常相似。如果您可以在调用lt_dec
的函数内构建证明,则无需使用safe_nth
。
你可以稍微缩短你的功能。
Fixpoint safe_nth' {X: Type} (xs: list X) (n: nat): n < length xs -> X :=
match xs, n with
| [], _ => fun pf => match nltz n pf with end
| x::_, 0 => fun _ => x
| x::xs', S n' => fun pf => safe_nth' xs' n' (predecessor_proof n' x xs' pf)
end.
我不确定最佳做法是什么,但如果您使用sig
,则会获得更整洁的代码。