【发布时间】:2015-07-30 12:42:32
【问题描述】:
我关心多态“全局”类值是否以及何时被共享/记忆,尤其是跨模块边界。我已阅读 this 和 this,但它们似乎并不能完全反映我的情况,而且我看到的一些行为与人们对答案的预期不同。
考虑一个公开了一个计算成本很高的值的类:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module A
import Debug.Trace
class Costly a where
costly :: a
instance Num i => Costly i where
-- an expensive (but non-recursive) computation
costly = trace "costly!" $ (repeat 1) !! 10000000
foo :: Int
foo = costly + 1
costlyInt :: Int
costlyInt = costly
还有一个单独的模块:
module B
import A
bar :: Int
bar = costly + 2
main = do
print foo
print bar
print costlyInt
print costlyInt
运行main 会产生两个单独的costly 评估(如跟踪所示):一个用于foo,另一个用于bar。我知道costlyInt 只是从foo 返回(评估的)costly,因为如果我从main 中删除print foo,那么第一个costlyInt 变得昂贵。 (我也可以通过将foo 的类型概括为Num a => a 来使costlyInt 执行单独的评估。)
我想我知道为什么会发生这种行为:Costly 的实例实际上是一个接受 Num 字典并生成 Costly 字典的函数。因此,当编译 bar 并解析对 costly 的引用时,ghc 会生成一个新的 Costly 字典,其中包含一个昂贵的 thunk。问题 1:我对此是否正确?
有几种方法可以只对costly 进行一次评估,包括:
- 将所有内容放在一个模块中。
- 删除
Num i实例约束,只定义一个Costly Int实例。
不幸的是,这些解决方案的类似物在我的程序中不可行——我有几个模块以多态形式使用类值,并且只有在顶级源文件中才最终使用具体类型。
还有一些变化不会减少评估次数,比如:
- 在实例中的
costly定义上使用 INLINE、INLINABLE 或 NOINLINE。 (我没想到这会奏效,但嘿,值得一试。) - 在实例定义中使用
SPECIALIZE instance Costly Intpragma。
后者让我感到惊讶——我希望它基本上等同于上面 确实 工作的第二项。也就是说,我认为它会生成一个特殊的Costly Int 字典,所有foo、bar 和costlyInt 都会共享它。我的问题2:我在这里错过了什么?
我的最后一个问题:是否有任何相对简单且万无一失的方法来获得我想要的东西,即跨模块共享特定具体类型的所有对 costly 的引用?从我目前看到的情况来看,我怀疑答案是否定的,但我仍然抱有希望。
【问题讨论】:
-
这个问题和my earlier one到底有什么不同?
-
据我所知,您问题的代价来自递归和字典传递,这会导致指数重新评估。一旦后者被固定,计算本身就会很快。但是,如果两个模块引用 fibsImplicitDict,它们会得到不同的 thunk,这些 thunk 将被单独评估。我负担不起,因为我的计算本质上是昂贵的(不是由于递归)。
-
PS —— 似乎存在带有约束的类实例这一事实在这里也发挥了作用。
-
这里需要使用类型类吗?如果它只有一个实例,则不需要它。
-
在我的真实程序中,是的——我有很多实例。 Costly 表示一个具有单位根的环,我有复数、整数 mod q、有限域等的实例。它们的单位根不够便宜,无法在所有地方重新计算。
标签: haskell polymorphism memoization