我的应用程序在使用 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(适用于任意Nums) where的函数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 实例的核心输出,并且 (*) 确实表示为嵌套 lambda,在顶层 lambda 中进行了 FFT 转换。所以看起来这至少可以被记忆。我还尝试过明智地和鲁莽地使用 bang 模式来试图强制评估无效。
附带说明一下,即使我能弄清楚如何让(*)memoize 成为它的第一个参数,它的定义方式还有另一个问题:想要使用Foo 数据类型的程序员必须了解这种 memoize 功能。如果她写
map (*c) xs
不会发生记忆。(它必须写成(map (c*) xs))现在我想起来了,我不完全确定 GHC 将如何重写(*c)版本,因为我已经 curried (*)。但我做了一个快速测试来验证两者(*c)并按(c*)预期工作:(c*)将c第一个 arg 设为*, 而(*c)将c第二个 arg 设为*. 所以问题在于如何编写乘法以确保记忆并不明显. 这只是中缀符号的固有缺点(以及参数*是对称的隐含假设)吗?
第二个不太紧迫的问题是我们将 (v*) 映射到标量列表的情况。在这种情况下,(希望)v 的 fft 将被计算和存储,即使它是不必要的,因为另一个被乘数是标量。有没有办法解决?
谢谢