我关心是否以及何时共享/记忆多态“全局”类值,特别是跨模块边界。我已经阅读了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
并解析对Costly
的引用时,ghc会生成一个新的costly
字典,其中包含一个昂贵的字典。问题1:我对此是否正确?
有几种方法只能对Num i
进行一次评估,包括:
Costly Int
实例约束,只需定义costly
实例。不幸的是,这些解决方案的类比在我的程序中是不可行的 - 我有几个模块以其多态形式使用类值,并且只在顶级源文件中是最终使用的具体类型。
还有一些更改不减少评估次数,例如:
SPECIALIZE instance Costly Int
定义上使用INLINE,INLINABLE或NOINLINE。 (我没想到这会起作用,但是,嘿,值得一试。)Costly Int
pragma。后者让我感到惊讶 - 我预计它基本上等同于上面 工作的第二项。也就是说,我认为它会生成一个特殊的foo
字典,所有bar
,costlyInt
和costly
都会共享。我的问题2:我在这里错过了什么?
我的最后一个问题:是否有任何相对简单和万无一失的方式来获得我想要的东西,即所有对模块共享的特定具体类型的{{1}}的引用?从我到目前为止看到的情况来看,我怀疑答案是否定的,但我仍然抱着希望。
答案 0 :(得分:6)
控制共享在GHC中很棘手。 GHC做了许多可以影响共享的优化(例如内联,浮动等)。
在这种情况下,为了回答为什么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
,同时避免计算内联。