为什么 f=(+) 不需要类型注释?



我的意思是,例如,

f :: (Enum a) => a -> a --without this line, there would be an error
f = succ

这是因为succ需要其参数是可枚举的(succ :: (Enum a) => a -> a

但对于(+)

f = (+) --ok

尽管(+)的声明是(+) :: (Num a) => a –> a –> a

我的意思是,为什么我不需要将f声明为f :: (Num a) => a –> a –> a

因为默认。Num是一个"defaultable"类型类,这意味着如果你让它不受约束,编译器会对你想把它用作哪种类型做出一些明智的猜测

:t f

在CCD_ 9中;它应该告诉你(IIRC)CCD_ 10。编译器不知道要使用哪个a,所以它猜测是Integer;既然成功了,那就跟猜测一样了。

为什么它没有推断出f的多态性类型?因为可怕的[1]单态性限制。当编译器看到时

f = (+)

它认为"f是一个值",这意味着它需要一个单一(单态)类型。Eta将定义扩展到

f x = (+) x

你会得到多态型

f :: Num a => a -> a -> a

同样,如果你扩展你的第一个定义

f x = succ x

你不再需要类型签名了。

[1] GHC文件中的实际名称!

我的意思是,为什么我不需要将f声明为(+) :: (Num a) => a –> a –> a

如果您声明了f的签名,那么您确实需要这样做。但如果不这样做,编译器将“猜测";签名本身–在这种情况下,这并不完全是值得注意的,因为它基本上可以只是复制&粘贴CCD_ 18的签名。这正是它将要做的。

或者至少它应该做什么。如果你打开了-XNoMonomorphism标志,它就会这样做。否则,可怕的单态性限制就会出现,因为f的定义是ConstantApplicativeForm=Value;这使得编译器将签名静音为它能找到的下一个最佳的非多态类型,即Integer -> Integer -> Integer。为了防止这种情况,您实际上应该为所有顶级函数手动提供正确的签名。这也防止了很多混乱,许多错误也变得不那么混乱了。

单态限制是的原因

f = succ

不会单独工作:因为它也有这种CAF形状,编译器不会试图推断正确的多态类型,而是试图找到一些具体的实例化来制作单态签名。但与Num不同,Enum类不提供默认实例。

可能的解决方案,按偏好排序:

  1. 始终添加签名你真的应该
  2. 启用-XNoMonomorphismRestriction
  3. 在表格f a = succ af a b = a+b中写出您的函数定义。因为有明确提到的论点,这些论点不符合CAF的条件,所以单态性限制不会生效

Haskell将Num约束默认为IntInteger,我忘记了是哪个。

最新更新