68

Haskell Wiki很好地解释了如何使用存在类型,但我不太了解它们背​​后的理论。

考虑这个存在类型的例子:

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

为我们可以转换为String. wiki 提到我们真正想要定义的类型是

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

4

3 回答 3

60

首先,看一下“Curry Howard 对应”,它指出计算机程序中的类型对应于直觉逻辑中的公式。直觉逻辑就像你在学校学到的“常规”逻辑,但没有排中律或双重否定消除法:

  • 不是公理:P ⇔ ¬¬P(但 P ⇒ ¬¬P 很好)

  • 不是公理:P ∨ ¬P

逻辑法则

你在德摩根定律的正确轨道上,但首先我们将使用它们来推导出一些新的定律。德摩根定律的相关版本是:

  • ∀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)

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

Con :: ∀x. t -> T

当我们把存在量词移到外面(前缀形式)时,它变成了一个全称量词。

所以以下理论上是等价的:

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

exists除了Haskell中没有 for 的语法。

在非直觉逻辑中,允许从 的类型推导出以下内容unCon

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

这是无效的原因是直觉逻辑中不允许这样的转换。所以unCon没有exists关键字就不可能写出for的类型,也不可能把类型签名放在prenex形式中。很难让类型检查器保证在这种情况下终止,这就是 Haskell 不支持任意存在量词的原因。

来源

“First-class Polymorphism with Type Inference”,Mark P. Jones,第 24 届 ACM SIGPLAN-SIGACT 编程语言原理研讨会论文集(网络

于 2012-05-25T11:53:21.437 回答
11

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

米切尔,约翰 C.;普洛特金,戈登 D。抽象类型具有存在类型,ACM Transactions on Programming Languages and Systems,Vol。10,第 3 期,1988 年 7 月,第 470-502 页

在高水平上,

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

于 2012-05-25T12:07:58.130 回答
10

您链接的haskell wiki文章中对此进行了说明。我将从中借用几行代码和注释并尝试解释一下。

data T = forall a. MkT a

在这里,您有一个T带有类型构造函数的类型MkT :: forall a. a -> T,对吧?MkT是(大致)一个函数,因此对于每个可能的 type 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)

如果您在示例中为已实现的类型类添加条件,则底线也是相同的。

于 2012-05-25T11:34:08.837 回答