如果我需要获取包含内存密集元素的大型列表的值,我对GHC抛出堆栈溢出感到有些惊讶。 我确实预计GHC会有TCO,所以我永远不会遇到这种情况。
为了简化大多数情况,请查看以下直接实现返回Fibonacci数的函数(取自HaskellWiki)。目标是显示百万分之一。
import Data.List
# elegant recursive definition
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
# a bit tricky using unfoldr from Data.List
fibs' = unfoldr (\(a,b) -> Just (a,(b,a+b))) (0,1)
# version using iterate
fibs'' = map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)
# calculate number by definition
fib_at 0 = 0
fib_at 1 = 1
fib_at n = fib_at (n-1) + fib_at (n-2)
main = do
{-- All following expressions abort with
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
--}
print $ fibs !! (10^6)
print . last $ take (10^6) fibs
print $ fibs' !! (10^6)
print $ fibs'' !! (10^6)
-- following expression does not finish after several
-- minutes
print $ fib_at (10^6)
源代码使用ghc -O2
进行编译。
我做错了什么?我想避免使用增加的堆栈大小或其他特定的编译器选项重新编译。
答案 0 :(得分:9)
These links here会很好地介绍太多thunk (空间泄漏)的问题。
如果你知道要注意什么(并且有一个不错的懒惰评估模型),那么解决它们很容易,例如:
{-# LANGUAGE BangPatterns #-}
import Data.List
fibs' = unfoldr (\(!a,!b) -> Just (a,(b,a+b))) (0,1)
main = do
print $ fibs' !! (10^6) -- no more stack overflow
答案 1 :(得分:3)
所有的定义(无用的fib_at除外)都会延迟所有+操作,这意味着当你选择了第一百万个元素时,它就是一个带有一百万个延迟添加的thunk。你应该尝试更严格的事情。
答案 2 :(得分:1)
正如其他人所指出的那样,Haskell很懒,你必须强制评估thunk以避免堆栈溢出。 在我看来,这个版本的纤维应该可以达到10 ^ 6:
fibs' = unfoldr (\(a,b) -> Just (seq a (a, (b, a + b) ))) (0,1)
我建议您研究wiki page on Folds,并查看seq function.