存在类型的理论基础是什么?


68

哈斯克尔维基做了解释如何使用存在类型的一个很好的工作,但我不太神交背后的理论。

考虑以下存在类型的示例:

data S = forall a. Show a => S a      -- (1)

为我们可以转换为的东西定义类型包装String。维基提到我们真正想要定义的是类似

data S = S (exists a. Show a => a)    -- (2)

即真正的“存在”类型-S大概我认为这是“数据构造函数接受Show实例存在的任何类型并将其包装”。实际上,您可能会编写如下的GADT:

data S where                          -- (3)
    S :: Show a => a -> S

我没有尝试编译它,但是似乎它应该可以工作。对我来说,GADT显然等效于我们要编写的代码(2)。

但是,对我来说,为什么(1)等同于(2)并不清楚。为什么将数据构造函数移到外部会forall变成exists

我能想到的最接近的是逻辑上的德摩根定律,在其中交换否定词和量词的顺序会将存在量词变成通用量词,反之亦然:

¬(∀x. px) ⇔ ∃x. ¬(px)

但是数据构造函数似乎与否定运算符完全不同。

可以使用forall代替不存在来定义存在类型的能力背后的理论是什么exists

Answers:


58

首先,看一下“柯里·霍华德对应”,它指出计算机程序中的类型对应于直觉逻辑中的公式。直觉逻辑就像您在学校中学到的“常规”逻辑一样,但是没有排除中或双重否定法则的规律:

  • 不是公理:P⇔¬P(但是P⇒¬P是可以的)

  • 不是公理:P∨P

逻辑定律

您在遵循DeMorgan的定律的道路上是正确的,但是首先我们将使用它们来推导一些新的定律。DeMorgan法律的相关版本为:

  • ∀x。P(x)=∃x ¬P(x)
  • ∃x。P(x)=∀x ¬P(x)

我们可以得出(∀x。P⇒Q(x))= P⇒(∀x。Q(x)):

  1. (∀x.P⇒Q(x))
  2. (∀x.¬P∨Q(x))
  3. ¬P∨(∀x。Q(x))
  4. P⇒(∀x.Q)

并且(∀x。Q(x)⇒P)=(∃x。Q(x))⇒P(此在下面使用):

  1. (∀x。Q(x)⇒P)
  2. (∀x。¬Q(x)∨P)
  3. (¬∀x.¬Q(x))∨P
  4. (∃x。Q(x))∨P
  5. (∃x。Q(x))⇒P

请注意,这些定律也具有直觉逻辑。我们得出的两个定律在下面的论文中被引用。

简单类型

最简单的类型易于使用。例如:

data T = Con Int | Nil

构造函数和访问器具有以下类型签名:

Con :: Int -> T
Nil :: T

unCon :: T -> Int
unCon (Con x) = x

类型构造器

现在让我们解决类型构造器。采取以下数据定义:

data T a = Con a | Nil

这将创建两个构造函数,

Con :: a -> T a
Nil :: T a

当然,在Haskell中,类型变量是隐式通用量化的,因此它们实际上是:

Con :: ∀a. a -> T a
Nil :: ∀a. T a

访问器也同样容易:

unCon :: ∀a. T a -> a
unCon (Con x) = x

量化类型

让我们将现有量词∃添加到我们的原始类型中(第一个,没有类型构造函数)。与其在看起来不像逻辑的类型定义中引入它,不如在看起来像逻辑的构造函数/访问器定义中引入它。我们稍后将修复数据定义以进行匹配。

相反Int,我们现在将使用∃x. t。在这里,t是某种类型的表达。

Con :: (∃x. t) -> T
unCon :: T -> (∃x. t)

根据逻辑规则(上面的第二个规则),我们可以将Conto的类型重写为:

Con :: ∀x. t -> T

当我们将存在量词移到外部(prenex形式)时,它变成了通用量词。

因此,以下内容在理论上是等效的:

data T = Con (exists x. t) | Nil
data T = forall x. Con t | Nil

除非exists在Haskell中没有语法。

在非直觉逻辑中,可以从以下类型得出以下内容unCon

unCon :: ∃ T -> t -- invalid!

之所以无效,是因为直觉逻辑中不允许进行这种转换。因此,不可能为unCon没有exists关键字而编写类型,并且不可能以prenex形式放置类型签名。很难保证类型检查器会在这种情况下终止,这就是Haskell不支持任意存在量词的原因。

资料来源

“具有类型推断的一流多态性”,Mark P. Jones,第24届ACM SIGPLAN-SIGACT编程语言原理研讨会论文集(web


1
非常正确,固定的。从“ P∧P”中得出矛盾非常容易,因为“ P∧P”通常被用作矛盾本身的定义。
Dietrich Epp 2012年

4
干杯。而且如果可以的话,双重否定消除和中间排除是等效的,因此经典逻辑公理仅提及其中之一。严格来说,它们也等效于皮尔斯定律,该定律具有仅使用逻辑含义的好习惯(定律如下:((P⇒Q)⇒P)⇒P)。皮尔斯定律有CH对应call/cc
维特斯(Vitus)2012年

2
@DietrichEpp:相关对编码在CH下的存在量化。例如,(∃n)n +1 = 2将被编码为Σ[ n ∶ ℕ ] (n + 1 ≡ 2),则证明包括证人和证明证人满足要求的证明,例如(1 , refl)(您可以读refl为“遵循定义”)。将其与对通用量化进行编码的相关函数进行比较。类型函数(n : ℕ) → n ≡ 0 + n将任意自然数转化为证明n = 0 + n
Vitus

1
我相信您应该陈述一个非定理: P⇔¬P等。是否为公理并不重要,重要的是它不是直觉逻辑定理。
彼得

1
我很难非正式地理解∀x. (Q(x) ⇒ P)∃x. Q(x) ⇒ P。我会这样翻译:For all x: if x is a red cat, then Hans is scared.If there exists at least one red cat, then Hans is scared。所以Q(x) = x is a red cat?P = Hans is scared。这对我来说很明显。不确定是否对其他人有帮助。
hasufell

11

Plotkin和Mitchell在他们的著名论文中建立了存在类型的语义,该语义使编程语言中的抽象类型与逻辑中的存在类型之间建立了联系,

米切尔(John C.)Plotkin,戈登D。抽象类型具有存在类型,ACM Transactions on编程语言和系统,第1卷。1988年7月,第10卷,第3期,第470-502页

在高层次上

抽象数据类型声明以Ada,Alphard,CLU和ML之类的类型化编程语言显示。这种形式的声明将标识符列表绑定到具有相关操作的类型,我们将其称为数据代数的复合“值”。我们使用二阶类型的Lambda演算SOL展示了如何为数据代数指定类型,作为参数传递以及作为函数调用结果返回。在这个过程中,我们讨论抽象数据类型声明的语义,并回顾类型化编程语言与构造逻辑之间的联系。


8

您链接的haskell Wiki文章中对此进行了说明。我将借用其中的一些代码行和注释,并尝试进行解释。

data T = forall a. MkT a

在这里,您有一个T带有类型构造函数的类型MkT :: forall a. a -> T,对吗?MkT(大致上)是一个函数,因此对于每种可能的类型a,该函数MkT都具有type a -> T。因此,我们同意通过使用该构造函数,我们可以构建类似[MkT 1, MkT 'c', MkT "hello"]type的值T

foo (MkT x) = ... -- what is the type of x?

但是,当您尝试提取(例如通过模式匹配)包装在a中的值时会发生什么T呢?它的类型注释仅表示T,而没有引用实际包含在其中的值的类型。我们只能就这样一个事实达成共识:无论它是什么,它都会有一种(也只有一种)类型。我们如何在Haskell中说明这一点?

x :: exists a. a

这只是说存在一个类型a,其x所属。在这一点上,应该很清楚,通过删除forall afromMkT的定义并显式指定包装值的类型(即exists a. a),我们可以实现相同的结果。

data T = MkT (exists a. a)

如果像示例中那样在实现的类型类上添加条件,则底线也相同。

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.