我有一个相当简单的函数来计算一个大列表的元素的平均值,使用两个累加器来保存到目前为止的总和和到目前为止的计数:
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 参数,从而避免由 thunk 引起的问题。但我仍然得到堆栈溢出。