我的应用程序在使用 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) 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 将被计算和存储,即使它是不必要的,因为另一个被乘数是标量。有没有办法解决?
谢谢