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

时间:2012-05-25 10:47:47

标签: haskell types type-systems existential-type quantifiers

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

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

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

我能想到的最接近的是逻辑中的De Morgan's Laws,其中交换否定和量词的顺序将存在量词转换为通用量词,反之亦然:

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

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

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

3 个答案:

答案 0 :(得分:54)

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

  • 不是公理: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)
  5. 和(∀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
    6. 请注意,这些法律也适用于直觉逻辑。我们得出的两个定律在下面的论文中引用。

      简单类型

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

      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
      

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

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

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

      除了Haskell中没有exists的语法。

      在非直觉逻辑中,允许从unCon的类型派生以下内容:

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

      这是无效的原因是因为在直觉逻辑中不允许这样的转换。因此,如果没有unCon关键字,则无法为exists编写类型,并且无法将类型签名置于prenex格式中。很难使类型检查器保证在这种条件下终止,这就是Haskell不支持任意存在量词的原因。

      来源

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

答案 1 :(得分:10)

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

  

Mitchell,John C。; Plotkin,Gordon D。; Abstract Types Have Existential Type,ACM编程语言和系统事务,卷。 10,   1988年7月第3号,第470-502页,

处于较高水平,

  

抽象数据类型声明出现在类型化的编程语言中,如Ada,Alphard,CLU和ML。这种形式的声明   将标识符列表绑定到具有关联操作的类型,a   复合“值”我们称之为数据代数。我们使用二阶打字   lambda演算SOL用于显示数据代数如何被赋予类型,   作为参数传递,并作为函数调用的结果返回。在   在这个过程中,我们讨论了抽象数据类型的语义   声明并检查类型化编程之间的连接   语言和建设性逻辑。

答案 2 :(得分:6)

在你链接的haskell wiki文章中说明了这一点。我将从中借用一些代码和注释,并尝试解释。

data T = forall a. MkT a

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

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

但是当您尝试提取(例如通过模式匹配)T中包含的值时会发生什么?它的类型注释只显示T,而不引用实际包含在其中的值的类型。我们只能就这样一个事实达成一致:无论它是什么,它都只有一种(而且只有一种);我们如何在Haskell中陈述这一点?

x :: exists a. a

这只是表示存在a所属的x类型。 此时应该清楚的是,通过从forall a的定义中删除MkT并明确指定包装值的类型(即exists a. a),我们能够达到同样的效果。

data T = MkT (exists a. a)

如果你在示例中添加了已实现的类型类的条件,那么底线也是一样的。