为什么GHC对^2操作发出「约束默认为Integer类型」的警告?
为什么GHC不把2推断为Int,而是默认用Integer?
Haskell中的数字字面量默认是多态的,2的类型为Num a => a,可以匹配任意属于Num类型类的具体类型。
在你当前的代码逻辑里,(^)运算符的第二个参数(也就是这里的2)的类型,完全不会影响函数的输入输出类型:(^) :: (Integral b, Num a) => a -> b -> a的返回值类型和第一个参数一致,和第二个参数b无关。你的squareOfSum签名只约束了输入输出的a属于Integral,完全没有对指数的类型b施加任何额外约束,GHC没办法通过上下文推断b到底是Int还是Integer,只能触发默认类型规则。
而Haskell标准的默认类型优先级列表是[Integer, Double],遇到Integral类型类的歧义约束时,会优先选择Integer,而不是Int。
为什么GHC需要类型默认机制?
如果没有类型默认,所有多态数字字面量的歧义场景都会直接编译报错。比如最简单的print 2,2是Num a => a,print只要求参数实现Show,没有额外约束确定a的具体类型,没有默认的话你必须手动写成print (2 :: Int)才能编译。类型默认是为了简化常规场景的代码编写,避免开发者给所有无歧义场景的数字手动加类型签名。
把2替换为2 :: Int是不是合理的解决方案?
如果你的使用场景下指数永远不会超过Int的取值范围,这么写完全没问题,确实可以消除警告。
但有更通用的更优方案:你要实现的是平方运算,完全可以不用(^)运算符,直接写成两个数相乘:
squareOfSum n = let s = sum [1..n] in s * s
这种写法完全不会产生类型歧义,也不需要额外加类型签名,同时保留了函数原有的Integral a => a -> a多态性,适配所有Integral类型的输入,比手动指定2的类型更灵活。
如果你确实需要保留(^)的写法,也可以根据业务场景选择2 :: Integer来避免潜在的溢出问题,比Int适用范围更广。
内容的提问来源于stack exchange,提问作者Heath Raftery

