Hello! I’m learning Haskell and trying to understand the performance of my programs.
I’m interested in a subset of unfoldr
like functions where each call to the “unfolding function” generates a list and a new seed which depends on the final value of the list. The function f
in the code below is like this. The function g
computes the same thing, but directly generates the next seed instead using arithmetic.
When I compile (using ghc -O2) and run the code below it takes around 1.5s to run. With f
replaced by g
in the main
function, it takes only 0.02s! I’m not sure why the performance difference is so large? In both cases I need to traverse the entire result list. With f
I only need to generate one more element of the list, which should not be very expensive.
I hope this question is interesting and I would love to understand this performance difference.
import Data.List (iterate, break, unfoldr)
target :: Int
target = 10000
f :: Int -> Int -> ([Int], Int)
f x s = (l, head t - target)
where (l, t) = break (> target) $ iterate (+ s) x
g :: Int -> Int -> ([Int], Int)
g x s = (takeWhile (<= target) $ iterate (+ s) x, s - ((target - x) `rem` s))
unfoldWith :: (a -> b -> (r, b)) -> [a] -> b -> [r]
unfoldWith f xs s = unfoldr f' (xs, s)
where f' (x:xs, s) = (\(r, s') -> Just (r, (xs, s'))) $ f x s
f' ([], _) = Nothing
main :: IO ()
main = print $ length . concat $ unfoldWith f [1..10000] 1