我担心是否以及何时共享/存储多态的“全局”类值,尤其是跨模块边界。我读过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
只是从costly
返回(评估后的)foo
,因为如果我从print foo
中删除main
,那么第一个costlyInt
就会变得很昂贵。 (通过将costlyInt
的类型概括为foo
,无论如何我也可以使Num a => a
进行单独的评估。)我想我知道为什么会发生这种情况:
Costly
的实例实际上是一个函数,它接受Num
字典并生成Costly
字典。因此,当编译bar
并解析对costly
的引用时,ghc会生成一个新的Costly
字典,其中包含昂贵的重磅内容。问题1:我对此是否正确?有几种方法可以仅对
costly
进行一次评估,包括:Num i
实例约束,仅定义一个Costly Int
实例。 不幸的是,这些解决方案的类似物在我的程序中不可行-我有几个模块以其多态形式使用类值,并且只有在顶级源文件中才最终使用具体类型。
还有一些更改不会减少评估的数量,例如:
costly
定义上使用INLINE,INLINABLE或NOINLINE。 (我没想到这会起作用,但是,嘿,值得一试。)SPECIALIZE instance Costly Int
编译指示。 后者令我感到惊讶-我希望它基本上等同于确实起作用的第二项。也就是说,我认为它将生成一个特殊的
Costly Int
字典,foo
,bar
和costlyInt
都将共享。我的问题2:我在这里想念什么?我的最后一个问题:是否有任何相对简单且简单的方法来获得我想要的东西,即所有对特定具体类型的
costly
的引用在模块之间共享?从目前为止我所看到的,我怀疑答案是否定的,但是我仍然抱有希望。 最佳答案
在GHC中,控制共享非常棘手。 GHC所做的许多优化可能会影响共享(例如,内联, float 内容等)。
在这种情况下,为回答为什么SPECIALIZE编译指示未达到预期效果的问题,让我们看一下B模块的核心,尤其是bar
函数:
Rec {
bar_xs
bar_xs = : x1_r3lO bar_xs
end Rec }
bar1 = $w!! bar_xs 10000000
-- ^^^ this repeats the computation. bar_xs is just repeat 1
bar =
case trace $fCostlyi2 bar1 of _ { I# x_aDm -> I# (+# x_aDm 2) }
-- ^^^ this is just the "costly!" string
那没有我们想要的那样。 GHC决定不内联复用
costly
,而只是内联costly
函数。因此,我们必须防止GHC内联代价高昂,否则计算将被重复。我们该怎么做?您可能会认为添加
{-# NOINLINE costly #-}
杂注就足够了,但是不幸的是,没有内联的专业化似乎无法很好地协同工作:A.hs:13:3: Warning:
Ignoring useless SPECIALISE pragma for NOINLINE function: ‘$ccostly’
但是,有一个技巧可以说服GHC做我们想要的事情:我们可以通过以下方式编写
costly
:instance Num i => Costly i where
-- an expensive (but non-recursive) computation
costly = memo where
memo :: i
memo = trace "costly!" $ (repeat 1) !! 10000000
{-# NOINLINE memo #-}
{-# SPECIALIZE instance Costly Int #-}
-- (this might require -XScopedTypeVariables)
这使我们可以专门研究
costly
,同时避免我们的计算内联。