У меня есть эта довольно простая функция для вычисления среднего числа элементов большого списка, используя два аккумулятора для хранения суммы до сих пор и подсчета до сих пор:
mean = go 0 0
where
go s l [] = s / fromIntegral l
go s l (x:xs) = go (s+x) (l+1) xs
main = do
putStrLn (show (mean [0..10000000]))
Теперь, на строгом языке, это будет хвост-рекурсивным, и проблем не будет. Однако, поскольку Haskell ленив, мой поисковик привел меня к пониманию того, что (s + x) и (l + 1) будут переданы по рекурсии в виде thunks. Таким образом, все это сбой и ожоги:
Stack space overflow: current size 8388608 bytes.
После дальнейшего поиска в Google я нашел seq
и $!
. Кажется, что я не понимаю, потому что все мои попытки использовать их в этом контексте оказались бесполезными, а сообщения об ошибках говорили о бесконечных типах.
Наконец, я нашел -XBangPatterns
, который решает все это путем изменения рекурсивного вызова:
go !s !l (x:xs) = go (s+x) (l+1) xs
Но я не доволен этим, так как -XBangPatterns
в настоящее время является расширением. Я хотел бы знать, как сделать оценку строгой без использования -XBangPatterns
. (И, может быть, тоже научитесь!)
Просто, чтобы вы поняли мое отсутствие понимания, вот что я пробовал (единственная попытка скомпилировать, то есть):
go s l (x:xs) = go (seq s (s+x)) (seq l (l+1)) xs
Из того, что я мог понять, seq должен здесь заставить оценивать аргументы s и l, тем самым избегая проблемы, вызванной thunks. Но я все равно получаю переполнение стека.