Suppose I have a list of all primes, defined as
primes :: (Enum α, Integral α) => [α]
primes = sieve [2..]
where sieve :: (Integral α) => [α] -> [α]
sieve [] = undefined
sieve (x:xs) = x : (sieve $ filter ((/= 0) . (flip mod x)) xs)
and I want to feed primes through multiple, different functions like:
sumOfPrimesLessThan :: (Integral α) => α -> α
sumOfPrimesLessThan n = sum $ takeWhile (< n) primes
or
productOfPrimesLessThan :: (Integral α) => α -> α
productOfPrimesLessThan n = foldl (*) 1 $ takeWhile (< n) primes
or something, as if by doing
main = do print (sumOfPrimesLessThan 1000 :: Integer)
print (productOfPrimesLessThan 1000 :: Integer)
Would Haskell, at any point in time, recalculate primes, or any part thereof? What would cached? What wouldn't be cached?
Addendum 0: Suppose I had another function
prime = flip elem primes
If prime were to be evaluated with different arguments, would each evaluation re-evaluate primes? For example:
allPrime = all prime
In your case (for Haskell GHC) the answer is that
primesis recalculated. Yet, if you had a monomorphic signature likeprimes :: [Int], that wouldn't be the case. Here's a way to debug this: importDebug.Traceand add thetracefunction toprimesNow, every time
primesis called, you will see "call to primes" printed out. Compiling your program (with or without optimizations), I get two calls toprimes.But why?
Haskell actually compiles your version of
primesto a function that takes one type argument, and so the use ofprimesinsidesumOfPrimesLessThanandproductOfPrimesLessThanis actually a function call (and function calls are not in general memoized in Haskell, for pretty obvious reasons). When you callsumOfPrimesLessThan 1000 :: Integer, for example, you actually give it two arguments: the value1000and the type argumentInteger.sumOfPrimesLessThanthen passes this second argument on to primes.On the other hand, if you had type signatures that were monomorphic, like
primes :: [Int],sumOfPrimesLessThan :: Int -> Int, andproductOfPrimesLessThan :: Int -> Int, Haskell compilesprimesdown to just a regular thunk value, so it only gets evaluated once.Here is another explanation of how Haskell represents values and functions on the inside I gave not long ago.
SPECIALIZEpragma (specific to GHC)Sometimes you would like to be able to tell GHC that even if your expression is polymorphic, you'd like it to be treated as monomorphic for a couple types. (So you kind of wish you had a second version
primes :: [Integer]even if in generalprimes :: (Enum α, Integral α) => [α].) You can specify this using theSPECIALIZEpragma:And now, just for the
Integertype,primeswill only be computed once. For other types (likeInt), we will still get the same behavior as before.In response to addendums
For multiple calls to
prime = flip elem primeswhereprimeis monomorphic ("instantiated"), you would still have only one call toprimes. Onceprimesis monomorphic, it can be shared everywhere. Also, beware the monomorphism restriction in yourallPrime = all primeexample - you'll need to specify which instance ofFoldablewant (allPrime = all primeis subtly different fromallPrime xs = all prime xs).