我在 Coq 的 SSReflect 扩展中发现了两个约定,它们看起来特别有用,但我还没有看到它们在较新的依赖类型语言(Lean、Agda、Idris)中被广泛采用。
首先,可能的谓词表示为布尔返回函数,而不是归纳定义的数据类型。这在默认情况下带来了可判定性,为计算证明开辟了更多机会,并通过避免证明引擎需要携带大量证明条款来提高检查性能。我看到的主要缺点是在证明时需要使用反射引理来操纵这些布尔谓词。
其次,具有不变量的数据类型被定义为包含简单数据类型和不变量证明的相关记录。例如,固定长度的序列在 SSReflect 中定义如下:
Structure tuple_of : Type := Tuple {tval :> seq T; _ : size tval == n}.
Aseq
和该序列长度为某个值的证明。这与 Idris 定义这种类型的方式相反:
data Vect : (len : Nat) -> (elem : Type) -> Type
一种依赖类型的数据结构,其中不变量是其类型的一部分。SSReflect 方法的一个优点是它允许重用,例如,许多为它们定义的函数seq
和关于它们的证明仍然可以与tuple
(通过在底层上操作seq
)一起使用,而使用 Idris 的方法,像reverse
,append
和类似的函数需要被重写为Vect
. Lean 实际上在其标准库中具有等效的 SSReflect 样式vector
,但它也具有 Idris 样式array
,该样式似乎在运行时具有优化的实现。
一本面向 SSReflect 的书甚至声称Vect n A
样式方法是一种反模式:
在依赖类型语言和 Coq 中,一个常见的反模式是将这些代数属性编码到数据类型和函数本身的定义中(这种方法的典型示例是长度索引列表)。虽然这种方法看起来很吸引人,因为它展示了依赖类型捕获数据类型和函数的某些属性的能力,但它本质上是不可扩展的,因为总会有另一个感兴趣的属性,这是设计者没有预见到的数据类型/函数,因此无论如何它都必须被编码为外部事实。这就是为什么我们提倡这种方法,其中数据类型和函数的定义尽可能接近程序员定义它们的方式,并且它们的所有必要属性都被单独证明。
因此,我的问题是,为什么这些方法没有被更广泛地采用。有没有我遗漏的缺点,或者它们的优点在对依赖模式匹配的支持比 Coq 更好的语言中不太重要?