假设我用 GHCi 写:
GHCi> let x = 1 + 2 :: Integer
GHCi> seq x ()
GHCi> :sprint x
GHCix = 3
按自然预期打印。
然而,
GHCi> let x = 1 + 2
GHCi> seq x ()
GHCi> :sprint x
产量x = _
这两个表达式之间的唯一区别是它们的类型(Integer
vs Num a => a
)。我的问题是到底发生了什么,为什么x
在后一个例子中似乎没有评估。
假设我用 GHCi 写:
GHCi> let x = 1 + 2 :: Integer
GHCi> seq x ()
GHCi> :sprint x
GHCix = 3
按自然预期打印。
然而,
GHCi> let x = 1 + 2
GHCi> seq x ()
GHCi> :sprint x
产量x = _
这两个表达式之间的唯一区别是它们的类型(Integer
vs Num a => a
)。我的问题是到底发生了什么,为什么x
在后一个例子中似乎没有评估。
主要问题是
let x = 1 + 2
定义了type 的多态值,forall a. Num a => a
它的计算类似于函数。
x
可以以不同的类型进行每次使用,例如x :: Int
,x :: Integer
等x :: Double
。这些结果不会以任何方式“缓存”,而是每次都重新计算,就像x
一个被多次调用的函数,可以这么说。
实际上,类型类的常见实现将这样的多态实现x
为函数
x :: NumDict a -> a
其中,NumDict a
上面的参数是由编译器自动添加的,并携带有关a
作为Num
类型的信息,包括如何执行加法、如何解释内部的整数文字a
等等。这称为“字典传递”实现。
因此,x
多次使用多态确实对应于多次调用函数,导致重新计算。为了避免这种情况,Haskell 中引入了(可怕的)单态限制,强制x
改为单态。MR 并不是一个完美的解决方案,并且在某些情况下会产生一些令人惊讶的类型错误。
为了缓解这个问题,在 GHCi 中默认禁用 MR,因为在 GHCi 中我们不太关心性能——可用性在那里更重要。然而,正如您所发现的,这会导致重新计算重新出现。