标题和标签应充分解释问题。
标题和标签应充分解释问题。
标题和标签应该充分解释问题。
嗯,并不完全正确。您使用了标签existential-type
,但是您提供的类型都不是存在类型。
这里已经有一些很好的答案了,所以我会采取不同的方法并更加正式。多态值本质上是类型上的函数,但是Haskell的语法将类型抽象和类型应用都隐含了起来,这使得问题变得模糊不清。我们将使用System F的符号,其中包含显式的类型抽象和类型应用。
例如,熟悉的map
函数将被写成
map :: ∀a. ∀b. (a → b) → [a] → [b]
map = Λa. Λb. λ(f :: a → b). λ(xs :: [a]). case xs of
[] → []
(y:ys) → f y : map @a @b f ys
map
现在是一个四个参数的函数:两种类型(a
和b
),一个函数和一个列表。我们使用Λ(大写lambda)来编写类型上的函数,使用λ来编写值上的函数。包含Λ的术语会产生包含∀的类型,就像包含λ的术语会产生包含→的类型一样。我使用@a
符号(如GHC Core中)表示应用类型参数。
因此,多态类型的值就像从类型到值的函数。调用多态函数的调用者可以选择类型参数,函数必须遵守。
那么,我们如何编写类型为∀a. [a]
的术语呢?我们知道包含∀的类型来自包含Λ的术语:
term1 :: ∀a. [a]
term1 = Λa. ?
?
的主体中,我们必须提供一个类型为[a]
的术语。也就是说,类型∀a。[a]
的术语意味着“给定任何类型a
,我将为您提供类型为[a]
的列表”。a
一无所知,因为它是从外部传递的参数。所以我们可以返回一个空列表。term1 = Λa. []
或未定义的值
term1 = Λa. undefined
或者是仅包含未定义值的列表
term1 = Λa. [undefined, undefined]
但除此之外没有太多。
那么[∀a. a]
呢?如果∀表示类型的函数,则[∀a. a]
是函数列表。我们可以提供任意少的函数:
term2 :: [∀a. a]
term2 = []
term2 = [f, g, h]
但是我们选择什么样的f
,g
和h
呢?
f :: ∀a. a
f = Λa. ?
a
的值,但是我们对类型a
一无所知。因此,我们唯一的选择是:f = Λa. undefined
因此,我们的term2
选项如下:
term2 :: [∀a. a]
term2 = []
term2 = [Λa. undefined]
term2 = [Λa. undefined, Λa. undefined]
等等,别忘了
term2 = undefined
普遍(∀)类型的值是从类型到值的函数。存在(∃)类型的值是类型和值的对。
更具体地说:类型为
∃x. T
是一对
(S, v)
这里是一个类型为 S 的存在类型签名,假设您在 T 中绑定类型变量 x 为 S,则有 v :: T
。
以下是几个具有该类型的术语:
term3 :: ∃a. a
term3 = (Int, 3)
term3 = (Char, 'x')
term3 = (∀a. a → Int, Λa. λ(x::a). 4)
∃a。 a
中。∀a. a
的值的用户处于非常优越的地位; 他们可以使用类型应用程序@T
选择任何特定类型,以获得类型为T
的术语。 类型为∀a. a
的值的生产者处于糟糕的位置:他们必须准备好生产任何所要求的类型,因此(如上一节中所述)唯一的选择是Λa. undefined
。∃a. a
的值的用户处于糟糕的位置;内部值是某些未知的特定类型,而不是灵活的多态值。 类型为∃a. a
的值的生产者处于非常优越的地位;正如我们上面所看到的,他们可以将任何他们喜欢的值放入该对中。type Something = ∃a. (a, a → a → a, a → String)
term4_a, term4_b :: Something
term4_a = (Int, (1, (+) @Int , show @Int))
term4_b = (String, ("foo", (++) @Char, λ(x::String). x))
triple :: Something → String
triple = λ(a, (x :: a, f :: a→a→a, out :: a→String)).
out (f (f x x) x)
结果:
triple term4_a ⇒ "3"
triple term4_b ⇒ "foofoofoo"
{-# LANGUAGE ExistentialQuantification #-}
data Something = forall a. MkThing a (a -> a -> a) (a -> String)
term_a, term_b :: Something
term_a = MkThing 1 (+) show
term_b = MkThing "foo" (++) id
triple :: Something -> String
triple (MkThing x f out) =
out (f (f x x) x)
我们的理论处理与实际应用有一些不同。类型应用、类型抽象和类型对再次是隐式的。此外,包装器使用forall
而不是exists
写得令人困惑。这涉及到我们声明了一个存在类型,但是数据构造函数具有全局类型:
MkThing :: forall a. a -> (a -> a -> a) -> (a -> String) -> Something
data SomeMonoid = forall a. (Monoid a, Show a) => MkMonoid a
如果您想了解理论方面,我强烈推荐 Pierce 的 类型和编程语言。如果想了解 GHC 中存在类型的讨论,请参阅GHC 手册和Haskell 维基。
forall
比作lambda表达式的原因。它们甚至具有几乎相同的作用域规则(一直延伸到右边,除非被括号包围)。这些东西正是我现在在脑海中阅读类型签名的方式,我很高兴Keegan花时间写了这篇文章。 - C. A. McCannforall a. [a]
表示对于任何单一类型,它是一个包含该类型的列表。这也是仅使用[a]
表示的意思,它是[]
,空列表数据构造函数的类型。[forall a. a]
意味着您拥有具有多态类型的值列表,即每个值都是任何可能类型的值,不一定与列表中的其他元素相同。没有可能的值可以具有类型forall a. a
,因此这也必须是一个空列表。forall a. a
表示。为了确保任何具有存在类型的内容仅在实际类型可用的范围内使用,编译器会防止存在类型“逃逸”。forall
量词器视为类似于lambda表达式--它引入一个新的类型变量,并将该标识符绑定在某个范围内。在该范围之外,标识符没有意义,这就是为什么forall a. a
非常无用的原因。forall a. a
的值,即 ⊥
。 - John Lseq
的虚构 Haskell。 - John L在类型中使用forall
意味着交集。因此forall a. a
是所有类型的交集,或者类似于Int ∩ String ∩ ...
,看起来会得到空集,但是每种类型都有一个额外的元素称为底部或⊥或Haskell中的undefined
。从这里我们得到forall a. a = {⊥}
。实际上,我们可以定义一个仅包含底部的类型:
data Zero
[forall a. a]
开始。它定义了一个底部列表或[Zero]
,其中包含元素[], [undefined], [undefined, undefined], ...
。让我们在ghci中检查一下:> let l = [undefined, undefined]::[Zero]
> :t l
l :: [Zero]
类似的,forall a. [a]
是所有列表类型的交集,由于 ∩[a] = [∩a]
,因此它再次是 [Zero]
。
为了进行最后的检查,让我们定义:
type L = forall a. [a]
type U = [forall a. a]
在ghci中:
> let l2 = [undefined, undefined]::L
> let l3 = [undefined, undefined]::U
> :t l2
l2 :: [a]
> :t l3
l3 :: U
请注意,l2::[a]
,解释是Haskell在所有多态类型前面放了一个隐式的forall
。