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

sqd*_*sqd 10 haskell functional-programming

我的意思是,例如,

f :: (Enum a) => a -> a --without this line, there would be an error
f = succ
Run Code Online (Sandbox Code Playgroud)

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

但对于 (+)

f = (+) --ok
Run Code Online (Sandbox Code Playgroud)

虽然(+)声明是(+) :: (Num a) => a –> a –> a.

我的意思是,我为什么不需要申报ff :: (Num a) => a –> a –> a

Jon*_*ast 14

因为违约. Num是一个'defaultable'类型类,这意味着如果你让它不受约束,编译器会做出一些关于你打算用它的类型的智能猜测.尝试将该定义放在模块中,然后运行

:t f
Run Code Online (Sandbox Code Playgroud)

in ghci; 它应该告诉你(IIRC)f :: Integer -> Integer -> Integer.编译器不知道a你想使用哪个,所以它猜对了Integer; 从那以后,它就是那个猜测.

为什么不推断出多态类型f?由于可怕的[1]单态性限制.当编译器看到

f = (+)
Run Code Online (Sandbox Code Playgroud)

它认为' f是一个价值',这意味着它需要一个单一的(单态)类型.Eta-将定义扩展为

f x = (+) x
Run Code Online (Sandbox Code Playgroud)

你会得到多态类型

f :: Num a => a -> a -> a
Run Code Online (Sandbox Code Playgroud)

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

f x = succ x
Run Code Online (Sandbox Code Playgroud)

你不再需要类型签名了.

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


lef*_*out 7

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

如果你声明签名,你需要这样做f.但是,如果你不这样做,编译器将"猜测"签名本身 - 在这种情况下,这并非显着,因为它基本上只能复制和粘贴签名(+).这正是它将要做的.

......或者至少它应该做什么.如果你有-XNoMonomorphism旗帜,它确实如此.否则,好的,可怕的单态限制步骤因为f's的定义是形状ConstantApplicativeForm = Value ; 这使得编译器将签名愚蠢到它能找到的下一个最好的非多态类型,即Integer -> Integer -> Integer.为了防止这种情况,您应该为所有顶级功能手动提供正确的签名.这也可以防止很多混乱,许多错误变得不那么混乱.

单态限制是原因

f = succ
Run Code Online (Sandbox Code Playgroud)

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

可能的解决方案,按优先顺序排

  1. 始终添加签名.你真的应该.
  2. 启用-XNoMonomorphismRestriction.
  3. 写你的函数定义形式f a = succ a,f a b = a+b.因为有明确提到的参数,这些参数不符合CAF的条件,因此单态性限制不会起作用.