У меня есть эта довольно простая функция для вычисления среднего значения элементов большого списка, использующего два аккумулятора для хранения суммы и количества пока:
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) будут передаваться по рекурсии как thunk. Таким образом, все это терпит крах и горит:
Stack space overflow: current size 8388608 bytes.
После дальнейшего поиска, я нашел 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. Но я все еще получаю переполнение стека.