私のアプリケーションは、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 で動作する) wherec=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 インスタンスのコア出力を確認したところ、(*) は実際には、最上位のラムダで FFT 変換を使用してネストされたラムダとして表されます。したがって、これは少なくともメモ化できるようです。また、バング パターンの賢明な使用と無謀な使用の両方を試みて、評価を強制的に無効にしようとしました。
補足として、最初の引数を memoize にする方法を理解できたとしても、(*)
その定義方法には別の問題があります。 Foo データ型を使用したいプログラマーは、このメモ化機能について知っておく必要があります。彼女が書いたら
map (*c) xs
メモ化は発生しません。(考えてみると、私はカリー化したので(map (c*) xs))
GHCがバージョンをどのように書き換えるか完全にはわかりません.しかし、両方を確認するために簡単なテストを行い、期待どおりに動作することを確認しました.最初の引数を, whileは2 番目の引数を にします. したがって問題は, メモ化を確実にするためにどのように乗算を記述すべきかが明らかでないことです. これは単に中置記法 (および への引数が対称であるという暗黙の仮定) に固有の欠点ですか?(*c)
(*)
(*c)
(c*)
(c*)
c
*
(*c)
c
*
*
2 つ目のあまり重要でない問題は、 (v*) をスカラーのリストにマップする場合です。この場合、他の被乗数がスカラーであるため不要ですが、(うまくいけば) v の fft が計算されて格納されます。これを回避する方法はありますか?
ありがとう