我有这个相当简单的函数来计算一个大名单的元素的平均值,使用两个蓄能器至今持有的总和,到目前为止计数:Haskell中的懒惰和尾递归,为什么会崩溃?
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
从我能理解,以次应该在这里强制评估s和l的论点,从而避免thunk造成的问题。但是我仍然遇到堆栈溢出。
好东西。很高兴知道GHC优化,并感谢 作为本书的链接,看起来像一个很好的资源。 但是,当我看着某事的帖子时,它让我觉得对我来说 看起来像使用seq应该打破尾递归。seq 必须在递归调用之后进行评估去,已经被 评估过了,所以从我对尾递归的理解来看,应该不再是尾递归了,因此就吹了堆栈。 当然不会发生,所以这里发生了一些事情。 Haskell特别对待seq吗?或者我只是简单地对 尾递归感到困惑? – Hisnessness 2009-10-24 21:41:00
seq在运行时不存在。这只是暗示使用不同的评估策略。你会得到完全不同的代码生成。 想象它更像是一个{ - #STRICT_WHNF# - } pragma – 2009-10-24 21:43:13