Haskell中的存在性与普遍量化类型

时间:2013-01-13 01:07:05

标签: haskell polymorphism existential-type

这些之间究竟有什么区别?我想我理解存在类型是如何工作的,它们就像在OO中有一个基类而没有办法降级。普遍类型有何不同?

2 个答案:

答案 0 :(得分:88)

这里的术语“通用”和“存在主义”来自predicate logic中类似命名的量词。

Universal quantification通常写成∀,您可以将其视为“为所有人”,并且大致意味着它的含义:在类似“∀x.......”的逻辑语句中代替对于所有可能的“x”,“......”都是正确的,你可以从量化的任何东西中选择。

Existential quantification通常写成∃,您可以将其视为“存在”,并表示在逻辑语句中类似于“∃x....”,无论代替“...... “对于某些未指明的”x“来说,这是从一组量化的东西中获得的。

在Haskell中,量化的东西是类型(至少忽略某些语言扩展),我们的逻辑语句也是类型,而不是“真”,我们认为“可以实现”。

因此,像forall a. a -> a这样的通用量化类型意味着,对于任何可能的类型“a”,我们可以实现类型为a -> a的函数。事实上我们可以:

id :: forall a. a -> a
id x = x

由于a是普遍量化的,我们对此一无所知,因此无法以任何方式检查论证。所以id是该类型唯一可能的函数(1)

在Haskell中,通用量化是“默认” - 签名中的任何类型变量都是隐式普遍量化的,这就是id的类型通常只写为a -> a的原因。这也称为parametric polymorphism,在Haskell中通常被称为“多态”,在其他一些语言(例如C#)中称为“泛型”。

存在量化类型如exists a. a -> a意味着,对于某些特定的类型“a”,我们可以实现一个类型为{{1}的函数}。任何功能都可以,所以我选择一个:

a -> a

...这当然是布尔人的“非”功能。但问题是我们不能使用它,因为我们所知道的类型“a”就是它存在。任何有关 类型的信息都已被丢弃,这意味着我们无法将func :: exists a. a -> a func True = False func False = True 应用于任何值。

这不是很有用。

那么可以我们用func做什么?好吧,我们知道它是一个输入和输出类型相同的函数,所以我们可以自己组合它,例如。从本质上讲,你可以用具有存在类型的东西做的唯一事情是你可以根据类型的非存在性部分做的事情。类似地,给定func类型的东西,我们可以找到它的长度,或者将它连接到它自己,或者删除一些元素,或者我们可以对任何列表做的任何其他事情。

最后一点让我们回到了通用量词,而Haskell (2)之所以没有直接存在的类型(上面的exists a. [a]完全是虚构的,唉) :因为具有存在量化类型的东西只能用于具有通用量化类型的操作,我们可以将类型exists写为exists a. a - 换句话说,对于所有结果类型forall r. (forall a. a -> r) -> r给定一个函数,对于所有类型r采用类型a的参数并返回类型a的值,我们可以得到类型为r的结果。

如果你不清楚为什么那些几乎是等价的,请注意r的整体类型不是普遍量化的 - 相反,它需要一个本身为a普遍量化的论证,然后它可以与它选择的任何特定类型一起使用。


顺便说一句,虽然Haskell实际上没有通常意义上的子类型概念,但我们可以将量词视为表达一种形式的子类型,其层次结构从普遍到具体到存在。 a类型的东西可以转换为任何其他类型,因此它可以被视为一切的子类型;另一方面,任何类型都可以转换为类型forall a. a,使其成为所有类型的父类型。当然,前者是不可能的(除了错误之外没有类型exists a. a的值)而后者是无用的(你不能对类型forall a. a做任何事情),但这个类比在纸上起作用至少。 :

请注意,存在类型和普遍量化参数之间的等价性与variance翻转函数输入的原因相同。


因此,基本思想大致是普遍量化的类型描述对任何类型都起作用的事物,而存在类型描述的是用特定但未知类型工作的事物。


1:嗯,不完全 - 只有当我们忽略导致错误的函数时,例如exists a. a,包括永不终止的函数,例如notId x = undefined。< / p>

2:好吧,GHC。没有扩展,Haskell只有隐含的通用量词,根本没有真正的方式来讨论存在类型。

答案 1 :(得分:2)

Bartosz Milewski在他的书中对Haskell为什么不需要存在量词提供了一些很好的见解:

  

在伪Haskell中:

(exists x. p x x) -> c ≅ forall x. p x x -> c
     

它告诉我们采用存在性类型的函数等效于多态函数。这是很合理的,因为必须准备这样的函数来处理可能以存在性类型编码的任何一种类型。这是告诉我们相同的原理,即接受求和类型的函数必须实现为case语句,并带有一个处理程序元组,该求和器用于求和中存在的每种类型。在这里,sum类型被替换为coend,处理程序族变为end或多态函数。

因此,Haskell中存在量化类型的一个例子是

data Sum = forall a. Constructor a    (i.e. forall a. (Constructor_a:: a -> Sum) ≅ Constructor:: (exists a. a) -> Sum)

可以看作是总和 data Sum = int | char | bool | ...。相比之下,Haskell中一种通用量化类型的示例是

data Product = Constructor (forall a. a)

可以作为产品data Product = int char bool ...使用。