I have some code that has structure equivalent to this:
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
i.e. f1 is a function that computes an expensive result based upon the first two arguments, and then uses this with the third argument. I had hoped that a partial application on the the first 2 arguments, then repeated application of the 3rd argument would result in the expensive computation only being run once. Unfortunately this is not the case:
test1 = do
putStrLn "test 1"
let p = f1 2 3
print (p 0.1)
print (p 0.2)
print (p 0.3)
results in:
*Main> test1
test 1
call cheap
call expensive
6.1
call cheap
call expensive
6.2
call cheap
call expensive
6.3
*Main>
I’ve come up with what seems to be a solution:
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)
resulting in:
*Main> test2
test 2
call cheap
call expensive
6.1
call cheap
6.2
call cheap
6.3
*Main>
But this seems quite messy. Is there a cleaner way I can avoid the redundant calls to the expensive calc?
You can just put the third argument inside the
let, so thatxis shared.(In this case
f2 a b = let x = expensive a b in cheap xworks too.)What you are looking for is compiler-driven partial evaluation, and that is a hard problem… at least it is sufficiently hard to implement properly that it isn’t in GHC.