I have code that has a structure equivalent to this:
import Debug.Trace newtype SomeExpensiveHiddenType = SCHT Double expensive :: Double -> Double -> SomeExpensiveHiddenType expensive ab = SCHT $ trace "call expensive" (*) ab cheap :: SomeExpensiveHiddenType -> Double -> Double cheap (SCHT x) c = trace "call cheap" (+) xc f1 :: Double -> Double -> Double -> Double f1 abc = let x = expensive ab in cheap xc
i.e. f1 is a function that calculates an expensive result based on the first two arguments, and then uses this with the third argument. I was hoping that a partial application of the first two arguments, and then reapplying the third argument, would result in the costly calculation being done only once. Unfortunately, this is not so:
test1 = do putStrLn "test 1" let p = f1 2 3 print (p 0.1) print (p 0.2) print (p 0.3)
leads to:
*Main> test1 test 1 call cheap call expensive 6.1 call cheap call expensive 6.2 call cheap call expensive 6.3 *Main>
I came up with what seems like a solution:
newtype X a = X { unX :: a } f2 :: Double -> Double -> X (Double -> Double) f2 ab = let x = expensive ab 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)
as a result of:
*Main> test2 test 2 call cheap call expensive 6.1 call cheap 6.2 call cheap 6.3 *Main>
But it seems pretty dirty. Is there a cleaner way to avoid redundant calls of expensive calc?
timbod
source share