记忆乘法

时间:2013-02-26 02:39:04

标签: haskell memoization currying

我的应用程序在使用FFT进行(昂贵)转换后将向量相乘。结果,当我写

f :: (Num a) => a -> [a] -> [a]
f c xs = map (c*) xs

我只想计算一次c的FFT,而不是xs的每个元素。确实没有必要为整个程序存储c的FFT,只需在本地范围内。

我尝试定义我的Num实例,如:

data Foo = Scalar c
         | Vec Bool v -- the bool indicates which domain v is in

instance Num Foo where
    (*) (Scalar c) = \x -> case x of
                         Scalar d -> Scalar (c*d)
                         Vec b v-> Vec b $ map (c*) v
    (*) v1 = let Vec True v = fft v1
             in \x -> case x of
                    Scalar d -> Vec True $ map (c*) v
                    v2 -> Vec True $ zipWith (*) v (fft v2)

然后,在一个应用程序中,我调用一个类似于f的函数(适用于任意Num s)c=Vec False v,我希望这个函数和如果我将f改为:

g :: Foo -> [Foo] -> [Foo]
g c xs = let c' = fft c
         in map (c'*) xs

函数g使fft c的记忆失效,并且比调用f要快得多(无论我如何定义(*))。我不明白f出了什么问题。是我在(*)实例中对Num的定义吗?是否与f在所有Nums上工作有关,因此GHC无法弄清楚如何部分计算(*)

注意:我检查了我的Num实例的核心输出,并且(*)确实表示为嵌套的lambdas,顶层lambda中的FFT转换。所以看起来这至少能够被记忆。我也尝试过使用爆炸模式进行明智和鲁莽的使用,试图强制评估无效。

作为旁注,即使我可以弄清楚如何使(*)记住它的第一个参数,还有另一个问题如何定义:程序员想要使用 Foo数据类型必须知道这个memoization功能。如果她写了

map (*c) xs

不会发生任何记忆。 (它必须写成(map (c*) xs))现在,我想起来,我不完全确定GHC会如何重写(*c)版本,因为我已经(*)。但我做得很快测试以验证(*c)(c*)是否按预期工作:(c*)使c成为*的第一个参数,而(*c)使c成为* 1}}第二个arg到*。所以问题是人们应该如何编写乘法来确保记忆是不明显的。这只是中缀符号的固有缺点(以及隐含的假设,即参数到{{1}}是对称的吗?

第二个不太紧迫的问题是我们将(v *)映射到标量列表的情况。在这种情况下,(希望)v的fft将被计算和存储,即使它是不必要的,因为另一个被乘数是标量。有没有办法解决这个问题?

由于

2 个答案:

答案 0 :(得分:2)

我相信stable-memo包可以解决您的问题。它不会使用相等而是通过引用标识来记忆值:

  

尽管大多数备忘录组合器基于相等性进行memoize,但是stable-memo基于之前是否已经将完全相同的参数传递给函数(即,在内存中是相同的参数)来实现。

当它们的密钥被垃圾收集时,它会自动删除记忆值:

  

stable-memo不会保留到目前为止看到的密钥,如果不再使用它们,它们将被垃圾收集。如果发生这种情况,将使用终结器来从备忘录表中删除相应的条目。

因此,如果您定义类似

的内容
fft = memo fft'
  where fft' = ... -- your old definition

你会得到你需要的东西:调用map (c *) xs会在第一次调用(*)时记住fft的计算,然后在(c *)的后续调用中重用它。如果c被垃圾收集,那么fft' c也是如此。

另请参阅this answerHow to add fields that only cache something to ADT?

答案 1 :(得分:1)

我可以看到两个可能阻止记忆的问题:

首先,f具有重载类型,适用于所有Num个实例。所以f不能使用memoization,除非它是专门的(通常需要SPECIALIZE pragma)或内联(可能会自动发生,但使用INLINE pragma更可靠)。

其次,(*)的{​​{1}}定义对第一个参数执行模式匹配,但Foo与未知f相乘。所以在c内,即使是专门的,也不会发生任何记忆。再次,它在很大程度上取决于f被内联,并且提供了f的具体参数,以便实际上可以出现内联。

所以我认为看看你究竟打电话给c是多么有帮助。请注意,如果使用两个参数定义f,则必须为其提供两个参数,否则无法内联。此外,有助于查看f的实际定义,因为您提及的Fooc不在范围内。