这里还有另一个因素,在 acfoltzer 包含的一些链接中提到过,但可能值得在这里明确说明。你遇到的效果是单态限制。当你说
let x = 5
你做了一个顶层的定义variable。 MR 坚持认为,当这些定义没有类型签名时,应该通过为未解析的类型变量选择(希望)合适的默认实例来专门化为单态值。相比之下,当您使用:t
要求推断类型时,不会施加此类限制或默认。所以
> :t 3
3 :: (Num t) => t
because 3
确实是重载的:任何数字类型都承认它。默认规则选择Integer
作为默认的数字类型,所以
> let x = 3
> :t x
x :: Integer
但现在让我们关掉 MR。
> :set -XNoMonomorphismRestriction
> let y = 3
> :t y
y :: (Num t) => t
如果没有 MR,定义就尽可能多态,就像重载一样3
。只是检查...
> :t y * (2.5 :: Float)
y * (2.5 :: Float) :: Float
> :t y * (3 :: Int)
y * (3 :: Int) :: Int
注意多态性y = 3
根据,在这些用途上有不同的专门化fromInteger
相关方法提供Num
实例。那是,y
与特定的表示无关3
,而是构建表示的方案3
。天真的编译,这是缓慢的秘诀,一些人将其视为 MR 的动机。
对于单态限制是大恶还是小恶的争论,我(局部假装)保持中立。我总是为顶级定义编写类型签名,因此我想要实现的目标没有任何歧义,并且 MR 不是重点。
当尝试学习类型系统如何工作时,将类型推断的各个方面分开确实非常有用。
“遵循计划”,将多态定义专门针对特定用例:一个相当稳健的约束解决问题,需要通过回链进行基本统一和实例解析;和
“猜测计划”,泛化类型以将多态类型方案分配给没有类型签名的定义:这是非常脆弱的,并且您越过基本的 Hindley-Milner 规则,使用类型类、更高级别的多态性、 GADT,事情变得更加奇怪。
了解第一个方法是如何运作的,并了解为什么第二个方法很困难,这是很好的做法。类型推断中的许多奇怪之处都与第二个相关,并且与诸如单态限制之类的启发式方法相关,这些启发式方法试图在面对歧义时提供有用的默认行为。