繁体   English   中英

Haskell 中的存在与通用量化类型

[英]Existential vs. Universally quantified types in Haskell

这些之间究竟有什么区别? 我想我理解存在类型是如何工作的,它们就像在面向对象中拥有一个基类而没有向下转换的方法。 通用类型有何不同?

这里的术语“普遍的”和“存在的”来自谓词逻辑中同名的量词。

全称量化通常写为 ∀,您可以将其读作“for all”,大致意思是它的发音:在类似于“∀x. ...”的逻辑语句中,任何代替“...”的东西对于所有可能的“x”都是正确的,您可以从任何正在量化的事物集中进行选择。

存在量化通常写为 ∃,您可以将其读作“存在”,这意味着在类似于“∃x....”的逻辑语句中,任何代替“...”的东西对于某些未指定的都是正确的“x”取自被量化的一组事物。

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

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

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

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

在 Haskell 中,通用量化是“默认”——签名中的任何类型变量都隐式地通用量化,这就是为什么id的类型通常只写成a -> a 这也称为参数多态性,在 Haskell 中通常简称为“多态性”,在其他一些语言(例如 C#)中称为“泛型”。

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

func :: exists a. a -> a
func True = False
func False = True

...这当然是布尔值的“非”函数。 但问题是我们不能这样使用它,因为我们对“a”类型的了解只是它存在。 任何关于它可能是哪种类型的信息都被丢弃了,这意味着我们不能将func应用于任何值。

这不是很有用。

所以,我们可以做什么用func 好吧,我们知道它是一个输入和输出类型相同的函数,因此我们可以将它与自身组合,例如。 从本质上讲,您可以对具有存在类型的事物做的唯一事情是您可以基于该类型的不存在部分做的事情。 类似地,给定某种类型的东西exists a. [a] exists a. [a]我们可以找到它的长度,或者将它连接到它本身,或者删除一些元素,或者我们可以对任何列表做的任何其他事情。

最后一点让我们回到全称量词,以及 Haskell (2)没有直接存在存在类型的原因(我上面的exists完全是虚构的,唉):因为具有存在性量化类型的事物只能与以下操作一起使用有普遍量化的类型,我们可以写出exists a. a的类型exists a. a exists a. a作为forall r. (forall a. a -> r) -> r forall r. (forall a. a -> r) -> r --in换句话说,所有的结果类型r给出一个函数,用于所有类型的a需要类型的参数a和返回类型的值r我们可以得到一个r类型的结果。

如果您不清楚为什么它们几乎相等,请注意,对于a而言,整体类型并没有普遍量化,而是需要一个参数,该参数本身对a进行a普遍量化,然后它可以与它选择的任何特定类型一起使用.


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

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


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


1:嗯,不完全是——仅当我们忽略导致错误的函数时,例如notId x = undefined ,包括永不终止的函数,例如loopForever x = loopForever x

2:嗯,GHC。 如果没有扩展,Haskell 就只有隐含的全称量词,根本没有真正的讨论存在类型的方式。

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

在伪 Haskell 中:

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

它告诉我们采用存在类型的函数等价于多态函数。 这是完全有道理的,因为这样的函数必须准备好处理可能以存在类型编码的任何一种类型。 同样的原则告诉我们,接受 sum 类型的函数必须作为 case 语句实现,并带有一个处理程序元组,一个用于 sum 中存在的每个类型。 在这里, 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 | ... data Sum = int | char | bool | ... data Sum = int | char | bool | ... 相比之下,Haskell 中通用量化类型的一个例子是

data Product = Constructor (forall a. a)

可以将其视为产品data Product = int char bool ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM