首先,让我们澄清一件事。如果我们在 GHCi 中定义函数而不是查询类型会发生什么?
> let x = map length . sum :: (Num [[a]]) => [[[a]]] -> [Int]
<interactive>:0:9:
Non type-variable argument in the constraint: Num [[a]]
(Use -XFlexibleContexts to permit this)
In an expression type signature: Num [[a]] => [[[a]]] -> [Int]
等等。换句话说,同样的事情。如果我们让 GHCi 推断定义的类型呢?
> let x = map length . sum
<interactive>:0:22:
No instance for (Num [[a0]])
arising from a use of `sum'
Possible fix: add an instance declaration for (Num [[a0]])
In the second argument of `(.)', namely `sum'
In the expression: map length . sum
这与加载包含没有类型签名的定义的文件所导致的错误大致相同。
这一切的结果是什么?好吧,想想它告诉你需要什么扩展的事实。GHC 能够识别该类型的含义,即使它默认拒绝该类型。我几乎不指望 GHC 会根据所使用的扩展组合使用完全不同的类型检查器,因此似乎很容易得出结论,除了禁用相关扩展之外,无缘无故地拒绝了违规类型。
GHCi 中的:t
命令不是编译过程的一部分——它是类型检查和推理系统的热线,让您询问假设代码的类型。它没有明显的理由根据扩展来任意限制自己,因为更通用的类型仍然可以提供信息,原因与上面的错误消息告诉您的原因相同,use -XFlexibleContexts to permit this
而不仅仅是syntax error in type constraint
.
顺便说一句,可能更有趣的是,在某些情况下,编译器会愉快地接受推断类型,但由于多种原因之一,推断类型实际上不能显式写出。
例如,禁用单态限制将允许您的示例推断其类型(与:t
所说的匹配),尽管该类型需要扩展才能手动编写。
另一个例子是where
函数定义的子句中的定义,它使用父函数的多态参数。它们自己的类型不是多态的,由外部范围内接收的参数决定,但父函数签名中的类型变量不在子句的范围内where
¹。可能还有其他例子。
ScopedTypeVariables
¹ 如果需要,可以使用扩展名和显式将来自父签名的类型变量带入范围forall
。