来自这个参考文献:严格的积极性
严格的阳性条件排除了诸如
data Bad : Set where
bad : (Bad → Bad) → Bad
A B C
-- A is in a negative position, B and C are OK
为什么A是负数?又为什么允许B?我了解为什么允许使用C。
来自这个参考文献:严格的积极性
严格的阳性条件排除了诸如
data Bad : Set where
bad : (Bad → Bad) → Bad
A B C
-- A is in a negative position, B and C are OK
为什么A是负数?又为什么允许B?我了解为什么允许使用C。
Answers:
首先用术语解释:否定和肯定立场来自逻辑。他们关于逻辑连接词的的对称性:在的一个表现不同乙。类似的事情发生在范畴论,在这里我们说的逆变和协变的分别,而不是消极和积极的。在物理学中,他们说的是“协变”和“反变”的量。因此,这是一个非常普遍的现象。程序员可能会将它们视为“输入”和“输出”。
现在介绍归纳数据类型。
将归纳数据类型视为一种代数结构:构造函数是将T的元素作为参数并产生T的新元素的运算。这与普通代数非常相似:加法取两个数字并产生一个数字。
在代数中,习惯上一个操作采用有限数量的参数,并且在大多数情况下,它采用零(恒定),一个(一元)或两个(二进制)参数。对于数据类型的构造函数,将其概括起来很方便。假设c
是数据类型的构造函数T
:
c
是一个常数,我们可以将其视为一个函数unit -> T
,或者等效地(empty -> T) -> T
,c
一元,我们可以认为是一个函数T -> T
,或等价地(unit -> T) -> T
,c
是二进制,我们可以将其视为一个函数T -> T -> T
,或者等效地T * T -> T
,或者等效地(bool -> T) -> T
,c
七个参数的构造函数,则可以将其视为一个函数(seven -> T) -> T
,该函数seven
是先前定义的带有七个元素的类型。c
,该构造函数可以无数个地接受很多参数,那就是一个函数(nat -> T) -> T
。这些示例表明,构造函数的一般形式应为
c : (A -> T) -> T
这里我们所说A
的元数的c
,我们认为c
作为一个构造函数A
类型的-many参数T
产生的元素T
。
这是非常重要的事情:必须在定义之前定义Arities T
,否则我们将无法确定构造函数应该做什么。如果有人试图拥有一个构造函数
broken: (T -> T) -> T
那么问题是“有多少论点broken
?” 没有好的答案。您可能会尝试用“它需要- T
许多参数” 来回答它,但这不会这样做,因为T
尚未定义。我们可能会尝试使用奇特的定点理论找出类型T
和内射函数来摆脱难题(T -> T) -> T
,并且会成功,但同时也会打破归纳原理T
。因此,尝试这样的事情是一个坏主意。
c
B
c : B * (A -> T) -> T
事实上,许多构造可以以这种方式被改写,但不是全部,我们需要一个步骤,即我们应该允许A
以依赖于B
:
c : (∑ (x : B), A x -> T) -> T
这是归纳类型构造函数的最终形式。正是W型是什么。形式是如此笼统,以至于我们只需要一个构造函数c
!确实,如果我们有两个
d' : (∑ (x : B'), A' x -> T) -> T
d'' : (∑ (x : B''), A'' x -> T) -> T
然后我们可以将它们组合成一个
d : (∑ (x : B), A x -> T) -> T
哪里
B := B' + B''
A(inl x) := A' x
A(inr x) := A'' x
顺便说一句,如果我们咖喱一般形式,我们看到它等效于
c : ∏ (x : B), ((A x -> T) -> T)
这更接近人们实际在证明助手中写下的内容。证明助手使我们能够以方便的方式写下构造函数,但这些构造函数与上面的一般形式等效(练习!)。
的第一次出现Bad
称为“负”,因为它表示函数自变量,即位于函数箭头的左侧(请参见Philip Wadler 免费提供的递归类型)。我想,术语“负位置”的由来从概念茎逆变(“反向”手段对面)。
不允许将类型定义为负值,因为可以使用该类型来编写非终止程序,即强规范化在存在时会失败(请参见下文)。顺便说一句,这就是规则“严格积极”的名称的原因:我们不允许出现负面立场。
我们允许第二次出现,Bad
因为它不会引起非终止,并且我们希望Bad
在递归数据类型的某个点(在其构造函数的最后一个箭头之前)使用要定义的类型()。
要明白,下面的定义也很重要不违反严格的规则积极性。
data Good : Set where
good : Good → Good → Good
该规则仅适用于构造函数的参数(Good
在这种情况下都是如此),不适用于构造函数本身(另请参见Adam Chlipala的“ 具有相关类型的认证编程 ”)。
另一个违反严格肯定性的例子:
data Strange : Set where
strange : ((Bool → Strange) → (ℕ → Strange)) → Strange
^^ ^
this Strange is ...this arrow
to the left of...
您可能还需要检查有关负职位的答案。
非严格肯定的声明会被拒绝,因为可以使用非严格声明来编写非终止函数。要了解如何使用上方的Bad数据类型编写循环定义,请参见BadInHaskell。
这是Ocaml中的一个类似示例,它显示了如何在没有(!)直接使用递归的情况下实现递归行为:
type boxed_fun =
| Box of (boxed_fun -> boxed_fun)
(* (!) in Ocaml the 'let' construct does not permit recursion;
one have to use the 'let rec' construct to bring
the name of the function under definition into scope
*)
let nonTerminating (bf:boxed_fun) : boxed_fun =
match bf with
Box f -> f bf
let loop = nonTerminating (Box nonTerminating)
该nonTerminating
函数从其参数“解包”该函数并将其添加到原始参数。在这里重要的是,大多数类型系统不允许将函数传递给它们自己,因此类似术语f f
将不会进行类型检查,因为没有类型f
可满足类型检查器。引入类型系统的原因之一是禁用自我应用程序(请参阅此处)。
像我们上面介绍的那样包装数据类型可以用来绕过这种通往不一致的道路。
我想补充一点,即非终止计算会给逻辑系统带来不一致性。对于Agda和Coq,False
归纳数据类型没有任何构造函数,因此您永远无法构建False类型的证明项。但是,如果允许使用非终止计算,则可以例如以这种方式(在Coq中)进行:
Fixpoint loop (n : nat) : False = loop n
然后进行类型检查loop 0
是否给定loop 0 : False
,因此在Curry-Howard对应关系下,这意味着我们证明了一个错误的主张。
结果:归纳定义的严格的正则规则可防止对逻辑造成灾难性的非终止计算。
A
并最终导致堆栈爆炸(使用基于堆栈的语言)。