对于任何A B : Prop
,sum A B
和sumbool A B
都是同构的,如下所示:
Definition from_sumbool (A B : Prop) (x : sumbool A B) : sum A B :=
match x with
| left l => inl l
| right r => inr r
end.
Definition to_sumbool (A B : Prop) (x : sum A B) : sumbool A B :=
match x with
| inl l => left l
| inr r => right r
end.
那么为什么我们有sumbool
?似乎仅仅是对sum
的限制,其中A B
是Prop
而不是Type
,结果是Set
而不是Type
。
“布尔”听起来像sumbools有2个元素。但是,仅sumbool True True
的情况。 sumbool False False
和sumbool False True
分别具有0和1个元素。
对于A B : Prop
,sum A B
的OCaml提取比sumbool A B
的OCaml更冗长。我没有明确的原因:我们假设提取知道A
的类型,而B
的类型为Prop
,因此可以使用与sumbool
相同的简化形式在这种情况下。
通常,Coq定义了3次相同的功能:分别针对Type
,Set
和Prop
。它适用于所有归纳类型(_rect
,_rec
和_ind
)的归纳方案。对于不相交的联合,这里有sum
,sumbool
和or
。这样可以记住的功能多了三倍。
答案 0 :(得分:4)
在某种程度上,我认为sumbool
的目的与sum
的用途不同,并且使用唯一的名称和符号突出显示并记录了这一事实。
sum
只是一个通用的求和类型,但是sumbool
旨在用作类似布尔值的结果,其中“ true”和“ false”值带有证据。因此,当您看到类似以下的库函数时:
Definition le_lt_dec n m : {n <= m} + {m < n}.
很明显,这种定义的目的是为了构造类似于布尔的决策值,我们可以像leb : nat -> nat -> bool
那样在计算中使用它,但是在每个条件分支。
更实际的是,类型sumbool : Prop -> Prop -> Set
允许Prop
的证据在编译/提取时被删除,而对于更一般的sum
则不会发生类型。
举一个愚蠢的例子,如果我们有一个head
函数,需要非零列表长度的证据:
Lemma nlt_0_r : forall n, ~(n < 0). Proof. intros n H. inversion H. Qed.
Definition head {A : Set} (l : list A) (E : 0 < length l) : A :=
match l return (0 < length l -> A) with
| x :: _ => fun _ => x
| nil => fun E1 => except (nlt_0_r _ E1)
end E.
我们想编写一个head_with_default
定义,使用sumbool
可能很自然:
Definition head_with_default {A : Set} (x : A) (l : list A) :=
match le_lt_dec (length l) 0 : {length l <= 0} + {0 < length l} with
| left _ => x
| right E => head l E
end.
我们还可以使用普通的sum
类型编写它:
Definition le_lt_dec' (n m : nat) : (n <= m) + (m < n). Admitted.
Definition head_with_default' {A : Set} (x : A) (l : list A) :=
match le_lt_dec' (length l) 0 : (length l <= 0) + (0 < length l) with
| inl _ => x
| inr E => head l E
end.
如果我们提取这两个定义,我们可以看到证据已从sumbool
版本中删除,但仍在sum
版本中随身携带:
Extraction head_with_default.
(* let head_with_default x l = *)
(* match le_lt_dec (length l) O with *)
(* | Left -> x *)
(* | Right -> head l *)
Extraction head_with_default'.
(* let head_with_default' x l = *)
(* match le_lt_dec' (length l) O with *)
(* | Inl _ -> x *)
(* | Inr _ -> head l *).
更新:在发表评论之前,请注意,提取中的这种差异并不是真正的“优化”。就像Coq看到的那样-在这种特殊情况下-Prop
中的sumbool
s可以被优化掉了,但是却没有在sum
中执行相同的优化,因为编译器没有还不够聪明。这是因为整个Coq逻辑都是基于这样的思想:在Prop
Universe中,证明值可以并且将被删除,但是在Set
Universe中,“证明”值很重要,并将在运行时反映出来。 / p>
进一步更新:现在,您可能会很好地问(就像您在进一步的评论中所做的那样),为什么不是在提取级别的优化?为什么不在Coq中使用单个sum
类型,然后更改提取算法,以使其擦除编译时已知为Prop
的所有类型。好吧,让我们尝试一下。假设使用上面的定义,我们写:
Inductive error := empty | missing.
Definition my_list := (inr 1 :: inr 2 :: inl missing :: inr 4 :: nil).
Definition sum_head := head_with_default' (inl empty) my_list.
提取看起来像这样:
type ('a, 'b) sum =
| Inl of 'a
| Inr of 'b
(** val my_list : (error, nat) sum list **)
let my_list = ...
(** val sum_head : (error, nat) sum **)
let sum_head =
head_with_default' (Inl Empty) my_list
现在,head_with_default'
的天真提取如上所述。如果我们想写出一个优化的版本,我们就不能重复使用sum
类型,因为它的构造函数具有错误的arity。我们需要使用删除的道具来生成优化的sum
类型:
type sumP =
| InlP
| InrP
let head_with_default' x l =
match le_lt_dec' (length l) O with
| InlP -> x
| InrP -> head l
这很好。当然,如果有人尝试创建nat + (x == 0)
,也称为sumor
:
Definition nat_or_zero (x : nat) : nat + (x = 0) :=
match x with
| O => inr eq_refl
| _ => inl x
end.
然后,我们需要sum
类型的第三种版本:
type ('a) sumSP =
| InlSP of 'a
| InrSP
let nat_or_zero x = match x with
| O -> InrSP
| S _ -> InlSP x
,除非我们有充分的理由拒绝sumPS
,否则我们将需要第四版(x==0) + nat
。
任何可能在sum
上运行的函数,例如:
Fixpoint list_lefts {A B : Type } (l : list (A + B)) : list A :=
match l with
| nil => nil
| inr x :: l' => list_lefts l'
| inl x :: l' => x :: list_lefts l'
end.
还需要提取多个版本。至少对于A : Set
,B : Set
和B : Prop
都可能有用:
(** val list_lefts : ('a1, 'a2) sum list -> 'a1 list **)
let rec list_lefts = function
| Nil -> Nil
| Cons (s, l') ->
(match s with
| Inl x -> Cons (x, (list_lefts l'))
| Inr _ -> list_lefts l')
(** val list_leftsSP : ('a1) sumSP list -> 'a1 list **)
let rec list_leftsSP = function
| Nil -> Nil
| Cons (s, l') ->
(match s with
| InlSP x -> Cons (x, (list_lefts l'))
| InrSP -> list_lefts l')
您可能会说另外两个没有用,但是如果有人不同意您并尝试将list_lefts'
应用于list ((x=0)+(x=1))
怎么办?显然,优化版本的第一个破解不能消除__
:
(** val list_leftsP : sum' list -> __ list **)
let rec list_leftsP = function
| Nil -> Nil
| Cons (s, l') ->
(match s with
| InlP -> Cons (__, (list_lefts l'))
| InrP -> list_lefts l')
但这仅仅是因为我们尚未提取list
的优化版本:
type listP =
| NilP
| ConsP of listP
让我们写:
(** val list_leftsP : sumP list -> listP **)
let rec list_leftsP = function
| Nil -> NilP
| Cons (s, l') ->
(match s with
| InlP -> ConsP (list_leftsP l')
| InrP -> list_leftsP l')
这表明list_leftsP
(以及我遗漏的第四个变体) 可能有用,因为它执行了计算{{ 1}}中的给定x=1
。
现在我们准备定义:
l : list ((x=0) + (x=1))
,并使用其16个版本之一(例如Definition ugh {A B C D : Type} : A + B -> C + D ->
A*C + A*D + B*C + B*D := ...
)和ughPPPS
四个版本的子集来表示其结果。但是,尚不清楚prod
的ML返回类型是否应该是幼稚的:
ughPPPS
无法删除类型为(((prodP ('d prodPS) sum) prodP sum) ('d prodPS) sum)
的无用术语,或者是否应将其优化为:
prodP
实际上,Coq可以走这条路线,归纳地跟踪类型对(((('d prodPS) sumPS) sumSP) ('d prodPS) sum)
与Props
的依赖关系,并根据程序中使用的所有变体生成必要的多次提取。取而代之的是,它要求程序员在Coq级别上决定哪些证明很重要(Sets
)或不重要(Set
),并且-过于频繁-需要类型,构造函数和函数的多种变体处理(某些)组合。结果是,提取将紧密反映Coq类型,而不是优化变体的Prop
色拉。 (如果您尝试在任何非Coq代码中使用提取,则是一个很大的优势。)