为什么 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 不提供默认实例。
可能的解决方案,按偏好排序:
- 总是添加签名。你真的应该。
- 启用
-XNoMonomorphismRestriction
.
- 以
f a = succ a
、f a b = a+b
的形式编写您的函数定义。因为有明确提到的参数,这些不符合 CAF 的条件,所以单态限制不会生效。
Haskell默认Num
约束为Int
或Integer
,忘记是哪个了。
因为违约。 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 文档的实际名称!
我的意思是,例如,
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 不提供默认实例。
可能的解决方案,按偏好排序:
- 总是添加签名。你真的应该。
- 启用
-XNoMonomorphismRestriction
. - 以
f a = succ a
、f a b = a+b
的形式编写您的函数定义。因为有明确提到的参数,这些不符合 CAF 的条件,所以单态限制不会生效。
Haskell默认Num
约束为Int
或Integer
,忘记是哪个了。
因为违约。 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 文档的实际名称!