我的意思是,例如,
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
类不提供默认实例。
可能的解决方案,按偏好排序:
- 始终添加签名你真的应该
- 启用
-XNoMonomorphismRestriction
- 在表格
f a = succ a
、f a b = a+b
中写出您的函数定义。因为有明确提到的论点,这些论点不符合CAF的条件,所以单态性限制不会生效
Haskell将Num
约束默认为Int
或Integer
,我忘记了是哪个。