按照Chlipala一书中GeneralRec一章中给出的例子,我试图编写mergesort算法。
这是我的代码
Require Import Nat.
Fixpoint insert (x:nat) (l: list nat) : list nat :=
match l with
| nil => x::nil
| y::l' => if leb x y then
x::l
else
y::(insert x l')
end.
Fixpoint merge (l1 l2 : list nat) : list nat :=
match l1 with
| nil => l2
| x::l1' => insert x (merge l1' l2)
end.
Fixpoint split (l : list nat) : list nat * list nat :=
match l with
| nil => (nil,nil)
| x::nil => (x::nil,nil)
| x::y::l' =>
let (ll,lr) := split l' in
(x::ll,y::lr)
end.
Definition lengthOrder (l1 l2 : list nat) :=
length l1 < length l2.
Theorem lengthOrder_wf : well_founded lengthOrder.
Admitted.
问题是不能用命令Fixpoint
编写mergeSort函数,因为函数没有在结构上减少:
Fixpoint mergeSort (l: list nat) : list nat :=
if leb (length l) 1 then l
else
let (ll,lr) := split l in
merge (mergeSort ll) (mergeSort lr).
相反,可以将Program Fixpoint
或Definition
命令与术语Fix
一起使用(如Chlipala一书中所述)。
但是,如果我正在写这个
Definition mergeSort : list nat -> list nat.
refine (Fix lengthOrder_wf (fun (l: list nat) => list nat)
(fun (l : list nat) => (fun mergeSort : (forall ls : list nat, lengthOrder ls l -> list nat )=>
if leb (length l) 1 then
let (ll,lr) := split l in
merge (mergeSort ll _) (mergeSort lr _)
else
l))).
我实现了不可能的目标:
2 subgoals, subgoal 1 (ID 65)
l : list nat
mergeSort : forall ls : list nat, lengthOrder ls l -> list nat
ll, lr : list nat
============================
lengthOrder ll l
subgoal 2 (ID 66) is:
lengthOrder lr l
这就是为什么Chlipala建议以这种方式改变mergeSort的定义:
Definition mergeSort : list nat -> list nat.
refine (Fix lengthOrder_wf (fun _ => list nat)
(fun (ls : list nat)
(mergeSort : forall ls' : list nat, lengthOrder ls' ls -> list nat) =>
if Compare_dec.le_lt_dec 2 (length ls)
then let lss := split ls in
merge (mergeSort (fst lss) _) (mergeSort (snd lss) _)
else ls)).
产生以下目标:
2 subgoals, subgoal 1 (ID 68)
ls : list nat
mergeSort : forall ls' : list nat, lengthOrder ls' ls -> list nat
l : 2 <= length ls
lss := split ls : list nat * list nat
============================
lengthOrder (fst lss) ls
subgoal 2 (ID 69) is:
lengthOrder (snd lss) ls
这个新定义对我来说听起来很神奇。所以我想知道:
答案 0 :(得分:5)
很容易看到您需要进行两项更改才能获得A. Chlipala的解决方案。
1)在执行split
时,您需要记住ll
和lr
来自拆分,否则它们将是一些任意列表,其不可能比原始列表短l
。
以下代码无法保存这类信息:
let (ll,lr) := split l in
merge (mergeSort ll _) (mergeSort lr _)
因此需要替换为
let lss := split ls in
merge (mergeSort (fst lss) _) (mergeSort (snd lss) _)
保留我们需要的东西。
由于Coq无法记住ll
和lr
来自split l
而导致失败,因为let (ll,lr)
只是match
伪装(参见手册,§2.2.3)。
回想一下,模式匹配的目的是(松散地说)
现在,在我们对其进行模式匹配之前,请注意split l
不会出现在目标或上下文中的任何位置。我们只是随意将它引入定义中。这就是为什么模式匹配并没有给我们任何东西 - 我们无法用{&#34;特殊情况&#34;}取代split l
。 ((ll,lr)
)在目标或上下文中,因为任何地方都没有split l
。
通过使用逻辑相等(=
):
(let (ll, lr) as s return (s = split l -> list nat) := split l in
fun split_eq => merge (mergeSort ll _) (mergeSort lr _)) eq_refl
这类似于使用remember
策略。我们摆脱了fst
和snd
,但这是一个巨大的矫枉过正,我不会推荐它。
2)我们需要证明的另一件事是ll
和lr
在l
时比2 <= length l
短。
由于if
- 表达式也是伪装的match
(它适用于完全两个构造函数的任何归纳数据类型),我们需要一些机制来记住leb 2 (length l) = true
分支中的then
。同样,由于我们在任何地方都没有leb
,因此这些信息会丢失。
该问题至少有两种可能的解决方案:
leb 2 (length l)
作为一个等式(就像我们在第一部分中所做的那样),或者bool
(因此它可以代表两种选择),但它也应该记住我们需要的一些其他信息。然后我们可以对比较结果进行模式匹配并提取信息,当然,在这种情况下,必须是2 <= length l
的证据。我们需要的是能够在m <= n
返回leb m n
的情况下携带true
证明的类型,以及m > n
的证据,否则。
标准库中有一种类型就是这样!它被称为sumbool
:
Inductive sumbool (A B : Prop) : Set :=
left : A -> {A} + {B} | right : B -> {A} + {B}
{A} + {B}
只是sumbool A B
的符号(句法糖)。
就像bool
一样,它有两个构造函数,但是它还记得两个命题A
和B
中的任何一个的证明。当您使用bool
对其进行案例分析时,它优于if
的优势显示:您在A
分支中获得了then
的证明,并证明了B
在else
分支中。换句话说,您可以使用事先保存的上下文,而bool
不承载任何上下文(仅在程序员的头脑中)。
我们需要的确如此!好吧,不在else
分支中,但我们想在2 <= length l
分支中获得then
。所以,让我们问Coq它是否已经有一个带有返回类型的比较函数:
Search (_ -> _ -> {_ <= _} + {_}).
(*
output:
le_lt_dec: forall n m : nat, {n <= m} + {m < n}
le_le_S_dec: forall n m : nat, {n <= m} + {S m <= n}
le_ge_dec: forall n m : nat, {n <= m} + {n >= m}
le_gt_dec: forall n m : nat, {n <= m} + {n > m}
le_dec: forall n m : nat, {n <= m} + {~ n <= m}
*)
五个结果中的任何都可以,因为我们只需要在一个案例中提供证据。
因此,我们可以将if leb 2 (length l) then ...
替换为if le_lt_dec 2 (length l) ...
并在证明上下文中获取2 <= length
,这样我们就可以完成证明。