7

これと同等の構造を持つコードがいくつかあります。

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> 

しかし、これはかなり厄介なようです。高価な計算への冗長な呼び出しを避けるためのよりクリーンな方法はありますか?

4

1 に答える 1

9

3 番目の引数を の中に入れるだけでlet、それxが共有されます。

f2 a b = let x = expensive a b in \c -> cheap x c

(この場合もf2 a b = let x = expensive a b in cheap x動作します。)


あなたが探しているのはコンパイラ主導の部分評価であり、それは難しい問題です...少なくともGHCにないことを適切に実装するのは十分に難しいです。

于 2012-09-05T13:37:46.250 に答える