これと同等の構造を持つコードがいくつかあります。
import Debug.Trace
newtype SomeExpensiveHiddenType = SCHT Double
expensive :: Double -> Double -> SomeExpensiveHiddenType
expensive a b = SCHT $ trace "call expensive" (*) a b
cheap :: SomeExpensiveHiddenType -> Double -> Double
cheap (SCHT x) c = trace "call cheap" (+) x c
f1 :: Double -> Double -> Double -> Double
f1 a b c = let x = expensive a b in cheap x c
つまりf1
、最初の 2 つの引数に基づいて高価な結果を計算し、これを 3 番目の引数で使用する関数です。最初の 2 つの引数に部分的に適用し、次に 3 番目の引数を繰り返し適用すると、高価な計算が 1 回だけ実行されることを期待していました。残念ながら、これは当てはまりません:
test1 = do
putStrLn "test 1"
let p = f1 2 3
print (p 0.1)
print (p 0.2)
print (p 0.3)
結果:
*Main> test1
test 1
call cheap
call expensive
6.1
call cheap
call expensive
6.2
call cheap
call expensive
6.3
*Main>
私は解決策と思われるものを思いつきました:
newtype X a = X { unX :: a }
f2 :: Double -> Double -> X (Double -> Double)
f2 a b = let x = expensive a b in X (cheap x)
test2 = do
putStrLn "test 2"
let p = unX $ f2 2 3
print (p 0.1)
print (p 0.2)
print (p 0.3)
その結果:
*Main> test2
test 2
call cheap
call expensive
6.1
call cheap
6.2
call cheap
6.3
*Main>
しかし、これはかなり厄介なようです。高価な計算への冗長な呼び出しを避けるためのよりクリーンな方法はありますか?