Lazy fold uses a lot of RAM. In Data.List
, foldl'
provides a left fold that uses strict evaluation. For example, the following computes the sum of 10 million zeros with little increase in RAM usage.
sum0 = foldl' ( ) 0 (replicate 10000000 0)
However, this does not seem to hold with complex data structures. For example, if we define addition for pairs of numbers and compute the sum of zero pairs, there is significant increase in RAM usage:
(x1,y1) < > (x2,y2) = (x1 x2,y1 y2)
sum00 = foldl' (< >) (0,0) (replicate 10000000 (0,0))
Why does that happen? Is there a way to decrease RAM usage?
CodePudding user response:
foldl'
only evaluates the intermediate state to weak head normal form—i.e. up to the first constructor. That's the most a generic function can do, and what functions that are called "strict" generally do. Evaluating (x1, y1) < > (x2, y2)
until it looks like a constructor gives (x1 x2, y1 y2)
, where the parts are still unevaluated (they have been "protected" by the (,)
). Through the iteration, foldl'
being strict keeps the state in the form (_, _)
instead of (_, _) < > (_, _) < > ...
, but the _
s grow into huge unevaluated terms of form _ _ _ ...
.
Modify < >
to evaluate the additions before it exposes the constructor.
(x1, y1) < > (x2, y2) = x `seq` y `seq` (x, y)
where x = x1 x2; y = y1 y2
-- or
(x1, y1) < > (x2, y2) = ((,) $! x1 y1) $! x2 y2
-- or (with deepseq package)
(x1, y1) < > (x2, y2) = force (x1 x2, y1 y2)
-- x `seq` y = y, but only if x reaches WHNF
-- usually, evaluating x `seq` y to WHNF evaluates x (to WHNF) before it returns the result of evaluating y to WHNF
-- though that's not the official definition of `seq`, since Haskell nominally doesn't have an evaluation strategy
-- (and GHC's actual `seq` may do something different if GHC is feeling smart)