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?
If you call a multi-parameter function with less than its total number of parameters, then you are partially applying the function. Here we call sum , our previously defined two-parameter function, with only one argument and receive a new function.
From HaskellWiki. A partial function is a function that is not defined for all possible arguments of the specified type. Examples in the Haskell standard library are: head , tail : undefined for empty lists. (!!) : undefined if the index is at least as big as the list length.
Application in JavaScript means that a function is applied to its argument and produces a return value. So partial application also has to do with applying functions to some of their arguments. The function that is applied partially is returned to be used later.
You can just put the third argument inside the let
, so that x
is shared.
f2 a b = let x = expensive a b in \c -> cheap x c
(In this case f2 a b = let x = expensive a b in cheap x
works 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With