严格的积极性


10

来自这个参考文献:严格的积极性

严格的阳性条件排除了诸如

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。


1
我不确定为什么将其称为“负数”,但是它产生的错误通常引起人们的注意:堆栈溢出:)此代码可能会导致无限扩展A并最终导致堆栈爆炸(使用基于堆栈的语言)。
wvxvw '16

那部分我知道您可以编写任意内容,因此计算将是无止境的。谢谢
Pushpa

1
我认为在您的问题正文中提及不终止将是一件好事。我已经根据您的评论更新了答案。
安东·特鲁诺夫

@wvxvw不一定,只要编译器实现尾部递归,它就可以永久运行而不会炸毁堆栈,例如,下面我在OCaml中的示例不会使堆栈爆炸。
Anton Trunov

1
@AntonTrunov可以肯定,这更像是对网站名称的双关语,而不是精确的尝试。
wvxvw

Answers:


17

首先用术语解释:否定肯定立场来自逻辑。他们关于逻辑连接词的的对称性:在一个表现不同。类似的事情发生在范畴论,在这里我们说的逆变协变的分别,而不是消极和积极的。在物理学中,他们说的是“协变”和“反变”的量。因此,这是一个非常普遍的现象。程序员可能会将它们视为“输入”和“输出”。ABAB

现在介绍归纳数据类型。

将归纳数据类型视为一种代数结构:构造函数是将T的元素作为参数并产生T的新元素的运算。这与普通代数非常相似:加法取两个数字并产生一个数字。TTT

在代数中,习惯上一个操作采用有限数量的参数,并且在大多数情况下,它采用零(恒定),一个(一元)或两个(二进制)参数。对于数据类型的构造函数,将其概括起来很方便。假设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。因此,尝试这样的事情是一个坏主意。

λvλvcB

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)

这更接近人们实际在证明助手中写下的内容。证明助手使我们能够以方便的方式写下构造函数,但这些构造函数与上面的一般形式等效(练习!)。


1
午餐后再次感谢安德烈(Andrej),这对我来说是最难的事情。干杯。
Pushpa

9

的第一次出现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... 

您可能还需要检查有关负职位的答案


有关非终止的更多信息...您所引用的页面包含一些说明(以及Haskell中的示例):

非严格肯定的声明会被拒绝,因为可以使用非严格声明来编写非终止函数。要了解如何使用上方的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对应关系下,这意味着我们证明了一个错误的主张。

结果:归纳定义的严格的正则规则可防止对逻辑造成灾难性的非终止计算。


现在我很困惑。特殊数据好:设置好:好→好→。我们将尽力在一个小时内了解并重新获得
答案

该规则不适用于构造函数本身,仅适用于其参数,即构造函数定义顶层的箭头无关紧要。我还添加了另一个(间接)违反示例。
Anton Trunov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.