My application multiplies vectors after a (costly) conversion using an FFT. As a result, when I write
f :: (Num a) => a -> [a] -> [a]
f c xs = map (c*) xs
I only want to compute the FFT of c
once, rather than for every element of xs
. There really isn't any need to store the FFT of c
for the entire program, just in the local scope.
I attempted to define my Num
instance like:
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)
Then, in an application, I call a function similar to f
(which works on arbitrary Num
s) where c=Vec False v
, and I expected that this would be just as fast as if I hack f
to:
g :: Foo -> [Foo] -> [Foo]
g c xs = let c' = fft c
in map (c'*) xs
The function g
makes the memoization of fft c
occur, and is much faster than calling f
(no matter how I define (*)
). I don't understand what is going wrong with f
. Is it my definition of (*)
in the Num
instance? Does it have something to do with f
working over all Nums, and GHC therefore being unable to figure out how to partially compute (*)
?
Note: I checked the core output for my Num instance, and (*) is indeed represented as nested lambdas with the FFT conversion in the top level lambda. So it looks like this is at least capable of being memoized. I have also tried both judicious and reckless use of bang patterns to attempt to force evaluation to no effect.
As a side note, even if I can figure out how to make (*)
memoize its first argument, there is still another problem with how it is defined: A programmer wanting to use the Foo data type has to know about this memoization capability. If she wrote
map (*c) xs
no memoization would occur. (It must be written as (map (c*) xs))
Now that I think about it, I'm not entirely sure how GHC would rewrite the (*c)
version since I have curried (*)
. But I did a quick test to verify that both (*c)
and (c*)
work as expected: (c*)
makes c
the first arg to *
, while (*c)
makes c
the second arg to *
. So the problem is that it is not obvious how one should write the multiplication to ensure memoization. Is this just an inherent downside to the infix notation (and the implicit assumption that the arguments to *
are symmetric)?
The second, less pressing issue is that the case where we map (v*) onto a list of scalars. In this case, (hopefully) the fft of v would be computed and stored, even though it is unnecessary since the other multiplicand is a scalar. Is there any way around this?
Thanks