我有一个相当简单的函数,用于计算一个大列表元素的平均值,使用两个累加器来保存迄今为止的总和和计数:
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.
经过进一步的谷歌搜索,我发现了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引起的问题。但我仍然遇到了堆栈溢出的问题。