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

Why doesn't f=(+) need a type annotation?

我的意思是,例如,

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

I mean, why don't I need to declare f as (+) :: (Num a) => a –> a –> a?

如果您声明 f 的签名,您确实需要这样做。但如果你不这样做,编译器将“猜测”签名本身——在这种情况下,这并不是什么了不起的事情,因为它基本上可以复制并粘贴 (+) 的签名。而这正是它要做的。

...或者至少它应该做什么。确实如此,前提是您打开了 -XNoMonomorphism 标志。否则,好吧, dreaded monomorphism restriction steps in because f's definition is of the shape ConstantApplicativeForm = Value;这使得编译器将签名简化为它可以找到的下一个最佳 非多态 类型,即 Integer -> Integer -> Integer。为了防止这种情况,您实际上应该为所有顶级函数手动提供正确的签名。这也避免了很多混乱,许多错误变得不那么混乱了。

单态限制是原因

f = succ

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

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

  1. 总是添加签名。你真的应该。
  2. 启用-XNoMonomorphismRestriction.
  3. f a = succ af a b = a+b 的形式编写您的函数定义。因为有明确提到的参数,这些不符合 CAF 的条件,所以单态限制不会生效。

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

因为违约。 Num 是一个 'defaultable' 类型 class,这意味着如果你让它不受约束,编译器将对你打算将其用作哪种类型进行一些智能猜测。尝试将该定义放入模块中,然后 运行

:t f

ghci;它应该告诉你 (IIRC) f :: Integer -> Integer -> Integer。编译器不知道你想使用哪个a,所以它猜测Integer;并且因为它有效,所以它与那个猜测一致。

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

f = (+)

它认为“f 是一个值”,这意味着它需要一个单一(单态)类型。 Eta-展开定义到

f x = (+) x

你会得到多态类型

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

类似地,如果你 eta-expand 你的第一个定义

f x = succ x

您不再需要类型签名。

[1] 来自 GHC 文档的实际名称!