我担心是否以及何时共享/存储多态的“全局”类值,尤其是跨模块边界。我读过thisthis,但是它们似乎并不能完全反映我的情况,而且我看到的行为与答案可能有所不同。

考虑一个暴露一个值的类,该值可能计算起来很昂贵:

{-# 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字典,foobarcostlyInt都将共享。我的问题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,同时避免我们的计算内联。

    08-07 21:54