首先,让我们澄清一件事。如果我们在 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 将父签名中的类型变量带入范围。