在Haskell中使用RankNTypes进行类型检查

时间:2014-07-15 14:09:56

标签: haskell types

我试图理解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

很容易理解为什么这会导致类型检查失败,因为l1l2应该有[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)类型的函数可以使用任何列表吗?

3 个答案:

答案 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 ~ Cd ~ D也选择c' ~ Cd' ~ D,那么该功能就是

check'' :: forall b . Eq b => ([C] -> b) -> ([D] -> b) -> [C] -> [D] -> Bool

不确定这是否能回答您的问题,但这是查看排名2类型的一种方式。