在软件基础Logic in Coq中,我们介绍了参数化的命题:
Definition is_three (n : nat) : Prop :=
n = 3.
Check is_three.
(* ===> nat -> Prop *)
这让我想起了依赖对类型,从Reading HoTT in Coq中我们定义了依赖对类型:
Inductive sigT {A:Type} (P:A -> Type) : Type :=
existT : forall x :A, P x -> sigT P.
有人可以解释他们之间的区别吗?在Coq的HoTT阅读中,它说:“由于我们尚未定义命题相等性,因此我们不能 这里有很多有趣的事情”,为什么没有命题相等性我们就不能做任何有趣的事情?
答案 0 :(得分:4)
现在让我们假装,HoTT代码使用A -> Prop
而不是A -> Type
;两者之间的差异与您的问题正交。
参数化命题P : A -> Prop
只是类型A
的元素的属性。除了上面简单的is_three
命题之外,我们还可以用这种方式表达自然数的更复杂的属性。例如:
Definition even (n : nat) : Prop :=
exists p, n = 2 * p.
Definition prime (n : nat) : Prop :=
n >= 2 /\
forall p q, n = p * q -> p = n \/ p = 1.
类型sigT A P
的类型允许我们将类型A
限制为满足属性P
的元素。例如,sigT nat even
是所有偶数的类型,sigT nat prime
是所有质数的类型,等等。在Coq中,属性是更原始的概念,而子集类型是sigT
是一个衍生概念。
在传统数学中,属性和子集的概念几乎可以混为一谈:说2是质数等于说它属于所有质数的集合。在Coq的类型理论中,情况并非如此,因为作为类型的元素不是是命题:例如,您不能陈述定理说2是{{1 }}。以下代码段引发错误:
sigT nat prime
(Lemma bogus : (2 : {x : nat & prime x}).
(* Error: *)
(* The term "2" has type "nat" while it is expected to have type *)
(* "{x : nat & prime x}". *)
是Coq标准库中定义的{ ... & ... }
类型的语法糖。)
我们可以得到的最接近的意思是可以从该类型中提取2:
sigT
其中Lemma fixed : exists x : {x : nat & prime x}, 2 = projT1 x.
是提取依赖对的第一个分量的函数。但是,这比简单地说2是质数更为麻烦:
projT1
通常,参数化命题在Coq中更有用,但是在某些情况下Lemma prime_two : prime 2.
类型派上用场;例如,当我们只关心满足某种属性的类型的元素时。假设您使用一种二叉搜索树在Coq中实现了一个关联映射。您可能首先定义任意树的类型sigT
:
tree
此类型定义一个二叉树,其节点存储自然数的键值对。要使用此类型实现用于查找元素,更新值等的功能,我们可以保持对树的键进行排序的不变性(即,左子树上的键小于的子键)。一个节点,右边的子树则相反。由于该树的用户不想考虑不满足该不变式的树,因此我们可以改用类型Inductive tree :
| Leaf : tree
| Node : tree -> nat -> nat -> tree -> tree.
,其中sigT tree well_formed
表示上述不变式。主要优点是,这简化了库的接口:与其说引物函数保留了不变式,还不如说一个独立的引理,它会自动以插入函数本身的类型表示;用户甚至无需费心争论使用接口构造的树就尊重不变性。
关于第二个问题,相等性是如此基础,以至于没有它就很难定义有趣的属性。例如,上面的属性well_formed : tree -> Prop
和even
都是使用相等定义的。