让我们从一个代码示例开始:
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
关键字。
基本上,forall
关键字有三个不同的常见用途(至少看起来是这样),每个用途都有自己的Haskell扩展:ScopedTypeVariables
、RankNTypes
/Rank2Types
和ExistentialQuantification
。
上面的代码在启用它们中的任何一个时都不会出现语法错误,但只有在启用ScopedTypeVariables
时才能通过类型检查。
作用域类型变量:
作用域类型变量帮助指定where
子句内部代码的类型。它使得val :: b
中的b
与foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
中的b
相同。
一个令人困惑的点:您可能会听说,当您从类型中省略forall
时,它实际上仍然隐含存在。(来自Norman的答案:"通常这些语言从多态类型中省略了forall")。这个说法是正确的,但它指的是forall
的其他用途,而不是ScopedTypeVariables
的用途。
Rank-N-Types:
让我们从mayb :: b -> (a -> b) -> Maybe a -> b
等价于mayb :: forall a b. b -> (a -> b) -> Maybe a -> b
开始,除非启用了ScopedTypeVariables
。
这意味着它适用于每个a
和b
。
假设您想要做这样的事情。
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>
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 不允许我们在 v
上应用 liftFunc
,因为 v :: b
而 liftFunc
需要一个 x
。我们真正想要的是我们的函数能够接受任何可能的 x
的函数!
{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)
因此,并非所有的x
都适用于liftTup
,而是它所获得的函数适用于所有的x
。
存在量词:
让我们举个例子:
{-# 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-Types有何不同?
ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
Couldnt match expected type 'a' against inferred type '[Char]'
...
使用Rank-N-Types,
forall a
表示你的表达式必须适合所有可能的
a
s。例如:
ghci> length ([] :: forall a. [a])
0
一个空列表可以作为任何类型的列表。
因此,在存在量化中,data
定义中的 forall
意味着,包含的值可以是任何适当的类型,而不是必须是所有适当的类型。