我试图理解Haskell中的RankNTypes
并找到了这个例子:
check :: Eq b => (forall a. [a] -> b) -> [c] -> [d] -> Bool
check f l1 l2 = f l1 == f l2
(如果我的理解是正确的,这相当于check :: forall b c d. Eq b => (forall a. [a] -> b) -> [c] -> [d] -> Bool
。)
好的,到目前为止一切顺利。现在,如果删除了显式forall a
,GHC会产生以下错误:
Could not deduce (c ~ a)
from the context (Eq b)
[…]
Could not deduce (d ~ a)
from the context (Eq b)
[…]
删除嵌套forall
时,类型签名变为
check :: forall a b c d. Eq b => ([a] -> b) -> [c] -> [d] -> Bool
很容易理解为什么这会导致类型检查失败,因为l1
和l2
应该有[a]
类型,以便我们将它们传递给f
,但为什么不是&#39 ;将f
的类型指定为(forall a. [a] ->b)
时的情况如何?事实上a
是否仅仅完全回答了问题?即类型检查器将接受
[c] -> b ~ (forall a. [a] -> b)
[d] -> b ~ (forall a. [a] -> b)
(编辑:修正。谢谢,博伊德!)
因为(forall a. a -> b)
类型的函数可以使用任何列表吗?
答案 0 :(得分:6)
当使用显式Rank2量化f = \xs -> ...
编写forall a. [a] -> b
时,您可以将其视为新函数
f = Λa -> \xs -> ...
其中Λ
是一个特殊的lambda,它使用类型参数来确定它将在函数体中使用哪个特定类型a
。每次调用函数时都会应用此类型参数,就像在每次调用时应用正常的lambda绑定一样。这就是GHC内部处理forall
的方式。
在明确的forall
版本中,f
可以在每次调用时应用于不同的类型参数,因此a
每次都可以解析为不同的类型,一次用于c
,一次用于d
。
在没有内部forall
的版本中,a
的此类型应用程序仅在调用check
时发生一次。因此,每次调用f
时,都必须使用相同的a
。当然,由于在不同类型的列表上调用f
,因此失败。
答案 1 :(得分:3)
很容易理解为什么这会导致类型检查失败,因为l1和l2应该有类型[a]让我们将它们传递给f,但是为什么在指定f的类型时不是这种情况(forall a。[a ] - > b)?
因为(forall a. [a] -> B)
类型可以与[C] -> B
和(单独)[D] -> B
统一。但是,类型[A] -> B
无法与[C] -> B
或[D] -> B
统一。
事实上a只是在parens中完全答案?
基本上。当你在forall范围“内部”时,你必须为每个类型变量选择一个特定的类型,但在外面你可以多次使用forall并且每次都选择不同的特定类型。
即。类型检查器将接受
[c] ~ (forall a. a -> b) [d] ~ (forall a. a -> b)
因为类型(forall a。a - > b)的函数可以列出任何列表吗?
小心。你似乎在那里失去了一些“[]”字符。此外,你并没有完全正确的统一。类型检查器将同时接受:
[C] -> B ~ (forall a. [a] -> B)
[D] -> B ~ (forall a. [a] -> B)
它也不接受:
[C] -> B ~ [A] -> B
[D] -> B ~ [A] -> B
答案 2 :(得分:1)
您可以在逆变场中重写通用量化,并在协变场中进行存在量化(在Haskell中不合法,但原则上)。
check' :: exists c' d'. forall b c d. Eq b
=> ([c'] -> b) -> ([d'] -> b) -> [c] -> [d] -> Bool
显而易见,这很有效:对于c ~ C
,d ~ D
也选择c' ~ C
和d' ~ D
,那么该功能就是
check'' :: forall b . Eq b => ([C] -> b) -> ([D] -> b) -> [C] -> [D] -> Bool
不确定这是否能回答您的问题,但这是查看排名2类型的一种方式。