在Haskell中重用/ memoization全局多态(类)值

时间:2015-05-18 21:12:22

标签: haskell polymorphism memoization

我关心是否以及何时共享/记忆多态“全局”类值,特别是跨模块边界。我已经阅读了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并解析对Costly的引用时,ghc会生成一个新的costly字典,其中包含一个昂贵的字典。问题1:我对此是否正确?

有几种方法只能对Num i进行一次评估,包括:

  • 将所有内容放在一个模块中。
  • 删除Costly Int实例约束,只需定义costly实例。

不幸的是,这些解决方案的类比在我的程序中是不可行的 - 我有几个模块以其多态形式使用类值,并且只在顶级源文件中是最终使用的具体类型。

还有一些更改减少评估次数,例如:

  • 在实例中的SPECIALIZE instance Costly Int定义上使用INLINE,INLINABLE或NOINLINE。 (我没想到这会起作用,但是,嘿,值得一试。)
  • 在实例定义中使用Costly Int pragma。

后者让我感到惊讶 - 我预计它基本上等同于上面 工作的第二项。也就是说,我认为它会生成一个特殊的foo字典,所有barcostlyIntcostly都会共享。我的问题2:我在这里错过了什么?

我的最后一个问题:是否有任何相对简单和万无一失的方式来获得我想要的东西,即所有对模块共享的特定具体类型的{{1}}的引用?从我到目前为止看到的情况来看,我怀疑答案是否定的,但我仍然抱着希望。

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,同时避免计算内联。