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