Haskell / GHC中的“ forall”关键字有什么作用?


312

我开始了解如何forall在这样的所谓“现有类型”中使用关键字:

data ShowBox = forall s. Show s => SB s

但是,这只是如何forall使用的一个子集,我根本无法在这样的事情上全神贯注于它的使用:

runST :: forall a. (forall s. ST s a) -> a

或解释为什么这些不同:

foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

或整个RankNTypes东西...

我倾向于使用清晰,无术语的英语,而不是学术环境中通常使用的那种语言。我尝试阅读的大多数解释(通过搜索引擎可以找到的解释)都存在以下问题:

  1. 他们不完整。他们解释如何使用这个关键字(如“生存型”)的一个组成部分,这使得直到我读码我感到高兴的是,使用它在一个完全不同的方式(比如runSTfoobar以上)。
  2. 假设我在本周流行的离散数学,范畴论或抽象代数的任何分支中都读到了最新的东西,他们的想法密密麻麻。(如果我从来不读的话“咨询文件无论执行的细节”再次,这将是太快了。)
  3. 它们的编写方式经常将甚至是简单的概念变成曲折而破碎的语法和语义。

所以...

关于实际问题。任何人都可以forall用清晰,简洁的英语来完全解释该关键字(或者,如果存在于某处,则指向我错过的如此清晰的解释),而不假定我是一个深深地迷惑于术语的数学家?


编辑添加:

以下是较高质量的答案中有两个突出的答案,但不幸的是,我只能选择其中一个作为最佳答案。 诺曼的答案是详尽而有用的,它以某种方式解释了事情,从而显示了该理论的一些基础,forall同时也向我展示了它的一些实际含义。 yairchu的答案覆盖了一个没人提及的区域(作用域类型变量),并通过代码和GHCi会话说明了所有概念。我会尽可能地选择两者。不幸的是,我不能这样做,在仔细查看了两个答案之后,由于示例性代码和附加说明,我认为yairchu的优势略微超出了Norman的优势。但是,这有点不公平,因为我确实需要两个答案才能理解这一点,以至于forall我在类型签名中看到它时都不会感到淡淡的恐惧感。


7
Haskell Wiki在这个主题上似乎非常适合初学者。
jhegedus

Answers:


263

让我们从一个代码示例开始:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

该代码在普通的Haskell 98中不会编译(语法错误)。它需要扩展来支持forall关键字。

基本上,关键字有3 种不同的常用用法forall(或至少看起来如此),每种都有自己的Haskell扩展名:ScopedTypeVariablesRankNTypes / Rank2TypesExistentialQuantification

上面的代码都不会在启用其中任何一个时发生语法错误,而只会在ScopedTypeVariables启用时进行类型检查。

作用域类型变量:

作用域类型变量有助于为where子句中的代码指定类型。这使得bval :: b同样的一个作为bfoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b

令人困惑的一点:您可能会听到,当您forall从类型中省略时,它实际上仍然隐式存在。(摘自Norman的回答:“通常,这些语言会忽略多态类型的所有内容”)。此声明是正确的,它涉及的其他用途forall,而不是该ScopedTypeVariables用途。

排名N型:

让我们先从这mayb :: b -> (a -> b) -> Maybe a -> b相当于mayb :: forall a b. b -> (a -> b) -> Maybe a -> b除外ScopedTypeVariables启用。

这意味着,它适用于所有的ab

假设您想做这样的事情。

ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

此类型必须是什么liftTup?是liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)。要了解原因,请尝试对其进行编码:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

“嗯。为什么GHC推断该元组必须包含两个相同类型的元组?让我们知道它们不一定必须是两个。”

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

嗯 所以这里GHC没有让我们应用liftFuncv,因为v :: bliftFunc希望的x。我们真的希望我们的函数能够接受任何可能的函数x

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

因此,这并非liftTup对所有人都有效x,而是它所获得的功能。

存在量化:

我们来看一个例子:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

与Rank-N-Type有什么不同?

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

使用Rank-N-Types forall a意味着您的表达式必须适合所有可能的as。例如:

ghci> length ([] :: forall a. [a])
0

空列表确实可以用作任何类型的列表。

因此,对于“存在量化”,定义中的foralls data表示所包含的值可以任何合适的类型,而不是必须所有合适的类型。


好的,我有六个小时,现在可以解码您的答案了。:)在你和诺曼之间,我得到了我一直在寻找的答案。谢谢。
我的正确观点

2
实际上,您ScopedTypeVariables看上去比实际情况要糟。如果您编写b -> (a -> b) -> Maybe a -> b带有此扩展名的类型,则它仍将等效于forall a b. b -> (a -> b) -> Maybe a -> b。但是,如果要引用相同的内容 b(而不是对其进行隐式量化),需要编写显式量化的版本。否则,STV将是一个非常侵入性的扩展。
nominolo 2010年

1
@nominolo:我不是要举止得体ScopedTypeVariables,也不认为这很糟糕。恕我直言,它对于编程过程特别是对于Haskell初学者来说是一个非常有用的工具,我很感激它的存在。
yairchu 2010年

2
这是一个比较老的问题(和答案),但可能值得对其进行更新,以反映以下事实:可以使用GADT(至少对我而言)可以简化量化的方式来表达存在类型。
dfeuer 2015年

1
我个人认为,将存在符号转换为GADT形式要比单独解释/理解要容易得多,但您当然可以自由考虑。
dfeuer 2015年

117

有人能用清晰,简洁的英语完全解释forall关键字吗?

(嗯,也许唐·斯图尔特可以。)

以下是简单明了的解释或的障碍forall

  • 这是一个量词。您必须至少有一点逻辑(谓词演算)才能看到通用或存在量词。如果您从未见过谓词演算或对量词不满意(并且我在博士学位资格考试中见过不满意的学生),那么对您来说,没有简单的解释forall

  • 这是一个类型量词。如果您还没有看过System F并获得了编写多态类型的实践,那么您会感到forall困惑。具有Haskell或ML的经验是不够的,因为通常这些语言会省略forallfrom多态类型。(在我看来,这是一个语言设计错误。)

  • 特别forall是在Haskell中,我觉得有些困惑。(我不是类型理论家,但是我的工作使我接触了很多类型理论,我对此非常满意。)对我而言,混淆的主要根源forall是用于编码一个我本人宁愿与一起写exists。涉及到量词和箭头的棘手的类型同构是有道理的,每次我想了解它时,我都必须查找事物并自己解决同构。

    如果您对类型同构的概念不满意,或者您没有任何关于类型同构的想法,那么使用forall将会使您感到困惑。

  • 虽然的一般概念forall总是相同的(绑定以引入类型变量),但是不同用途的细节可能会有很大差异。非正式英语不是一个很好的解释变化的工具。要真正了解正在发生的事情,您需要一些数学。在这种情况下,相关的数学可以在本杰明·皮尔斯的介绍性文本《类型和编程语言》中找到,这是一本非常好的书。

至于你的例子

  • runST 应该会伤到你的头 在野外很少发现高等级的类型(箭头左侧的全部)。我鼓励您阅读介绍的文章runST“惰性功能状态线程”。这是一篇非常好的论文,它将使您对runST特定类型和一般较高级别的类型有更好的直觉。解释需要几页,而且做得很好,在这里我不会尝试进行压缩。

  • 考虑

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))

    如果我打电话bar,我可以简单地选择任何a我喜欢的类型,然后将其传递给type a到type 的函数a。例如,我可以传递函数(+1)或函数reverse。您可以想到forall“我现在就选择类型”。(用于选择类型的技术字是实例化的。)

    调用的限制foo更加严格:to的参数foo 必须是多态函数。对于这种类型,我只能传递给的函数fooid或总是有偏差或错误的函数,例如undefined。其原因是,与foo,则forall是箭头的左侧,这样的调用foo我不明白选择什么样a的,而它的实现foo是得到选择什么样a的。因为forall是在箭头的左侧,而不是在箭头上方bar,所以实例化发生在函数的主体中,而不是在调用位置。

摘要:一个完整的解释forall关键字需要数学,只能由人谁研究数学来理解。没有数学就连部分解释都很难理解。但是,也许我的部分非数学解释会有所帮助。请继续阅读Launchbury和Peyton Jones runST


附录:行话在“上方”,“下方”,“左侧”。这些与文本类型的编写方式无关,与抽象语法树无关。在抽象语法中,a forall使用类型变量的名称,然后在所有变量“下方”有一个完整类型。箭头采用两种类型(参数和结果类型)并形成新类型(函数类型)。参数类型是箭头的“左侧”;它是箭头在抽象语法树中的左子元素。

例子:

  • 在中forall a . [a] -> [a],所有人位于箭头上方;箭头左侧是[a]

  • forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f

    括号中的类型将称为“箭头左侧的全部”。(我正在使用的优化器中使用这种类型。)


其实我在上面/下面/左边都无需考虑。我是一个呆板家伙,是的,但是一个呆板家伙以前必须与这些东西搏斗。(除其他外,编写了ASN.1编译器。)但是,谢谢您的附录。
只是我的正确观点2010年

12
@JUST谢谢,但我写后代。我遇到了不止一个程序员,他认为forall a . [a] -> [a],最重要的是箭头的左侧。
Norman Ramsey 2010年

好的,详细讨论您的答案,现在,我必须衷心感谢诺曼。很多东西已经下跌到位了现在大声点击,以及东西,我还是不明白,我至少认识到,我没有意思去理解它,并刚刚越过forall在这种情况下作为,有效,行噪声。我将仔细检查您链接到的那篇论文(也感谢您的链接!),看看它是否属于我的理解范围。荣誉
只是我的正确观点2010年

10
我向左看,从字面上看,我向左看。因此,直到您说“解析树”,这对我来说还是非常不清楚的。
Paul Nathan 2010年

感谢皮尔斯的书的指针。它对系统F的解释非常清楚。它解释了为什么exists从未实施。(这不是系统F的一部分!)在Haskell中,系统F的一部分被隐含了,但这forall是不能完全扫除的一件事。好像他们是从Hindley-Milner开始的那样,它将forall被隐含起来,然后选择了功能更强大的类型系统,这使我们当中那些研究FOL的“全部”和“存在”并在此停止的人们感到困惑。
T_S_

50

我的原始答案:

任何人都可以用简洁明了的英语完全解释forall关键字吗?

正如诺曼指出的那样,很难从类型理论中为技术术语提供清晰,清晰的英语解释。我们都在努力。

关于'forall',实际上只需要记住一件事:它将类型绑定到某个范围。一旦您了解了这一点,一切都会变得很容易。在类型级别上,它等同于“ lambda”(或“ let”的形式)-Norman Ramsey使用“ left” /“ above”概念在其出色的回答中传达了相同的范围概念。

“ forall”的大多数用法非常简单,您可以在《 GHC用户手册》 S7.8中找到它们的介绍,特别嵌套形式的“ forall” 的出色的S7.8.5

在Haskell中,当类型被普遍量化时,我们通常不使用类型绑定器,例如:

length :: forall a. [a] -> Int

等效于:

length :: [a] -> Int

而已。

由于您现在可以将类型变量绑定到某个范围,因此可以具有除顶级(“ 通用量化 ”)之外的范围,例如您的第一个示例,其中类型变量仅在数据结构中可见。这允许使用隐藏类型(“ 存在类型 ”)。或者,我们可以具有绑定的任意嵌套(“等级N类型”)。

要深入了解类型系统,您将需要学习一些术语。那就是计算机科学的本质。但是,类似于上面的简单用法,应该能够通过与价值水平上的“让”类比来直观地把握。一个很棒的介绍是Launchbury和Peyton Jones


4
从技术上说,length :: forall a. [a] -> Int并不等同于length :: [a] -> IntScopedTypeVariables启用。当forall a.存在时,会影响lengthwhere子句(如果有),并更改其中命名的类型变量的含义a
yairchu 2010年

2
确实。ScopedTypeVariables使故事复杂化了一点。
唐·斯图尔特

3
@DonStewart,在您的解释中,“将类型绑定到某个范围”是否可以更好地表述为““将类型变量绑定到某个范围”?
Romildo 2013年

31

他们充满了这样的假设:我在本周流行的离散数学,范畴论或抽象代数的任何分支中都读到了最新的文章。(如果我再也没有读过“请咨询论文以了解实施细节”这句话,那就太早了。)

嗯,那简单的一阶逻辑呢?forall相对于通用量化而言,这很明显,并且在这种情况下,术语“ 存在性”也更有意义,尽管如果有一个exists关键字,它会不太尴尬。量化究竟是通用的还是存在的,取决于量化器相对于在功能箭头的哪一侧使用变量的位置的位置,这一切都有些混乱。

因此,如果这无济于事,或者您只是不喜欢符号逻辑,那么从更具功能性的编程角度来看,您可以将类型变量视为只是(隐式)函数的类型参数。在这种意义上,带有类型参数的函数通常出于某种原因使用大写lambda编写,在此将其写为/\

因此,请考虑以下id功能:

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

我们可以将其重写为lambda,将“类型参数”移出类型签名并添加内联类型注释:

id = /\a -> (\x -> x) :: a -> a

这是做的同样的事情const

const = /\a b -> (\x y -> x) :: a -> b -> a

所以你 bar功能可能是这样的:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

注意给定的函数类型 bar作为参数bar的类型取决于的type参数。考虑一下是否有这样的事情:

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

bar2是将函数应用于类型Char,因此给bar2除type以外的任何类型参数Char都会导致类型错误。

另一方面,这就是 foo可能是这样的:

foo = (\f -> (f Char 't', f Bool True))

不像barfoo实际上根本没有任何类型参数!它本身具有一个功能带有类型参数的函数,然后将该函数应用于两种不同的类型。

因此,当您forall在类型签名中看到a 时,只需将其视为类型签名lambda表达式即可。就像常规lambda一样,forallextends 的范围会尽可能向右扩展,直到括起括号,并且就像在常规lambda中绑定的变量一样,由a绑定的类型变量forall仅在量化表达式内。


scriptum后:也许您可能会想知道-现在我们正在考虑采用类型参数的函数,为什么我们不对这些参数做比将它们放入类型签名更有趣的事情?答案是我们可以!

将类型变量和标签放在一起并返回新类型的函数类型构造函数,您可以编写如下代码:

Either = /\a b -> ...

但是我们需要全新的符号,因为这样的类型的写法就像 Either a b)已经暗示了“将函数Either应用于这些参数”。

另一方面,在类型参数上进行某种“模式匹配”并为不同类型返回不同值的函数是类型类方法。对我/\上面的语法稍作扩展,就可以看到以下内容:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

就个人而言,我认为我更喜欢Haskell的实际语法...

“模式匹配”其类型参数并返回任意现有类型的函数类型族函数依赖关系 -在前一种情况下,它甚至看起来已经非常像函数定义。


1
这里很有趣。这使我对这个问题有另一个攻击角度,从长远来看可能会取得成果。谢谢。
只是我的正确意见,2010年

@KennyTM:或者λ,但是GHC的unicode语法扩展不支持这一点,因为λ是一个字母,这是一个不幸的事实,该假设也可以应用于我的假设大lambda抽象。因此/\ 类似于\ 。我想我本可以用,但是我想避免谓词演算...
CA McCann 2010年

29

这是您可能已经熟悉的简单明了的简要说明。

forall关键字真的只在一个Haskell的方式使用。当您看到它时,它总是意味着同一件事。

通用量化

普遍量化类型是一种形式的forall a. f a。可以将该类型的值视为一个类型 a为参数并返回type 的函数f a。除了在Haskell中,这些类型参数是由类型系统隐式传递的。无论接收到哪种类型,此“函数”都必须为您提供相同的值,因此该值是多态的

例如,考虑类型forall a. [a]。该类型的值采用另一种类型a,并返回相同类型的元素的列表a。当然,只有一种可能的实现。它必须给您空列表,因为a它绝对可以是任何类型。空列表是唯一在元素类型上是多态的列表值(因为它没有元素)。

还是类型forall a. a -> a。这种函数的调用者同时提供type a和type 的值a。然后,实现必须返回相同类型的值a。再次只有一种可能的实现。它必须返回与给定的相同值。

存在量化

一个存在性量化类型将有形式exists a. f a,如果哈斯克尔支持该符号。可以将这种类型的值视为由一个类型和一个type值组成的(或“产品”)。af a

例如,如果您有一个type的值,那么您将拥有exists a. [a]某种类型的元素的列表。它可以是任何类型,但是即使您不知道它是什么,您也可以对这样的列表做很多事情。您可以反转它,也可以计算元素的数量,或者执行任何其他不依赖于元素类型的列表操作。

好,等一下。为什么Haskell forall用来表示“存在”类型,如下所示?

data ShowBox = forall s. Show s => SB s

这可能会造成混淆,但实际上是在描述数据构造函数类型SB

SB :: forall s. Show s => s -> ShowBox

构造完成后,您可以认为类型的值ShowBox由两部分组成。它是一个类型s以及type 的值s。换句话说,它是一个存在量化类型的值。如果Haskell支持该表示法,则ShowBox可以将其写为exists s. Show s => s

runST 和朋友

鉴于此,这些有什么不同?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

让我们先来bar。它接受类型a和类型的函数a -> a,并产生类型的值(Char, Bool)。我们可以选择Int作为,a并为其提供类型函数Int -> Int。但是foo是不同的。它要求实现foo能够将所需的任何类型传递给我们提供的函数。因此,我们可以合理地赋予它的唯一功能是id

现在,我们应该能够解决以下类型的含义runST

runST :: forall a. (forall s. ST s a) -> a

因此,无论我们给出哪种类型,runST都必须能够产生type的值。为此,它使用类型必须确实以某种方式产生的参数。而且,无论实现决定将as赋予哪种类型,它都必须能够产生type的值。aaforall s. ST s aaarunSTs

好那怎么了 好处是,这runST对类型的调用者施加了约束,因为该类型a根本不能涉及该类型s。例如,您不能将其传递为type的值ST s [s]。实际上,这意味着的实现runST可以自由地使用type的值进行突变s。该类型保证此突变是本地实施的runST

的类型runST等级2多态类型的示例,因为其参数的类型包含forall量词。上述的类型foo也属于等级2。普通的多态类型(与的一样)bar为等级1,但是如果要求参数的类型具有其自己的forall量词,则它变为等级2 。并且,如果函数接受等级2参数,则其类型为等级3,依此类推。通常,采用rank的多态参数的类型n具有rank n + 1


11

任何人都可以用清晰,简洁的英语完全解释forall关键字(或者,如果它存在于某处,则指向我错过的如此清晰的解释),而不假定我是一个专业的数学家?

我将尝试解释forallHaskell及其类型系统中的含义以及可能的应用。

但是,在您了解我之前,我想向您介绍Runar Bjarnason题为“ Constraints Liberate,Liberties Constrain ” 的非常易于理解的演讲。尽管没有提到,但演讲中充满了来自现实世界中用例的示例以及Scala中支持该声明的示例forall。我将尝试解释以下forall观点。

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

消化并相信此陈述可以进行以下解释,这一点非常重要,因此,我敦促您注意谈话(至少是其中的一部分)。

现在,一个非常常见的示例说明了Haskell类型系统的表现力,即该类型签名:

foo :: a -> a

有人说,给定这种类型签名,只有一个函数可以满足这种类型,那就是该identity函数或更广为人知的功能id

在学习Haskell的初期,我一直想知道以下功能:

foo 5 = 6

foo True = False

他们都满足上述类型签名,那么为什么Haskell的人会声称它是 id只有一个满足类型签名?

这是因为forall类型签名中有一个隐式隐藏。实际类型为:

id :: forall a. a -> a

所以,现在让我们回到陈述:约束解放,自由约束

将其转换为类型系统,此语句变为:

类型级别的约束在术语级别变成自由

类型级别的自由成为术语级别的约束


让我们尝试证明第一个语句:

类型级别的约束。

因此对我们的类型签名施加了约束

foo :: (Num a) => a -> a

在学期一级成为自由,这 使我们可以自由或灵活地编写所有这些内容

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

通过约束a任何其他类型类等可以观察到相同

现在,此类型签名的含义foo :: (Num a) => a -> a是:

a , st a -> a, a  Num

这被称为存在量化,它转化为存在某些实例,a该实例在馈入某种类型的函数时a东西返回相同类型的东西,而这些实例全部属于数字集。

因此,我们可以看到添加了一个约束(a应该属于Numbers集合),解放了术语级别以具有多种可能的实现。


现在来看第二个陈述,而该陈述实际上带有以下解释forall

类型级别的自由成为术语级别的约束

现在让我们在类型级别上释放函数:

foo :: forall a. a -> a

现在,这转换为:

a , a -> a

这意味着该类型签名的实现应a -> a适用于所有情况。

所以现在这开始在学期层面上限制我们。我们不能再写

foo 5 = 7

因为如果将放a为,此实现将无法满足Boola可以是a Char或a [Char]或自定义数据类型。在任何情况下,它都应返回类似类型的内容。在类型级别的这种自由就是所谓的通用量化,唯一可以满足此要求的功能是

foo a = a

这就是通常所说的identity功能


因此forallliberty类型级别的,其实际目的是constrain特定实现的术语级别。


9

此关键字之所以有不同的用法,是因为它实际上在至少两个不同的类型系统扩展中使用:更高级别的类型和存在。

最好是分别阅读和理解这两件事,而不是试图同时解释为什么“ forall”是合适的语法解释。


3

存在性如何存在?

对于存在定量,定义中的foralls data表示所包含的值可以任何合适的类型,而不是必须所有合适的类型。- yachiru的答案

原因解释 foralldata的定义是同构的(exists a. a)(伪哈斯克尔)中可以找到维基教科书的“哈斯克尔/存在性量化类型”

以下是逐字摘要:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

模式匹配/解构时MkT x,类型是什么x什么?

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

x 可以是任何类型(如 forall),因此其类型为:

x :: exists a. a -- (pseudo-Haskell)

因此,以下是同构的:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

永远意味着一切

我对所有这些的简单解释是“ forall真的意味着'为所有人'”。为了使一个重要的区别是冲击forall定义与功能应用

A forall表示定义值或函数必须是多态的。

如果要定义的事物是多态,则意味着该值必须对所有合适的值都有效a,这是非常严格的。

如果要定义的事物是多态函数,则意味着该函数必须对所有合适的函数都有效a,而没有那么严格的限制,因为仅仅因为该函数是多态的并不意味着所应用的参数就必须是多态的。也就是说,如果该函数对all有效a,则可以应用任何合适的函数a该功能。但是,参数的类型只能在函数定义中选择一次。

如果a forall在函数参数的类型(即a Rank2Type)之内,则意味着所应用的参数必须是真正多态的,以与forall均值定义为多态的思想相一致。在这种情况下,可以在函数定义中多次选择参数的类型(“并且由函数的实现选择”,如Norman所指出的

因此,存在性data定义允许任何 a形式的原因是因为数据构造函数是一个多态函数

MkT :: forall a. a -> T

MkT :: a -> *

这意味着任何a都可以应用于该功能。相对于多态

valueT :: forall a. [a]

一种值T :: a

这意味着valueT 的定义必须是多态的。在这种情况下,valueT可以定义为[]所有类型的空列表。

[] :: [t]

差异性

尽管意为forall是一致的ExistentialQuantificationRankNType,existentials有差别,因为在data构造函数可以在模式匹配使用。如文件中所述 ghc用户指南中所述

模式匹配时,每个模式匹配都会为每个存在的类型变量引入一个新的独特类型。这些类型不能与任何其他类型统一,也不能脱离模式匹配的范围。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.