Haskell:重复一个函数很多次而没有stackoverflow

时间:2012-01-18 12:19:01

标签: haskell stack-overflow

作为Haskell的新手,我试图多次迭代一个函数(例如逻辑映射)。在命令式语言中,这将是一个简单的循环,但在Haskell中,我最终会出现堆栈溢出。以此代码为例:

main  = print $ iter 1000000

f x = 4.0*x*(1.0-x)

iter :: Int -> Double
iter 0 = 0.3
iter n = f $ iter (n-1)

对于少量迭代,代码可以工作,但是对于一百万次迭代,我得到了堆栈空间溢出:

Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

我无法理解为什么会这样。这里的尾递归应该没问题。 也许问题是懒惰的评价。我通过在不同位置插入$!seq来试验强制严格评估的几种方法,但没有成功。

Haskell多次迭代函数的方法是什么?

我已尝试过相关帖子中的建议:herehere,但我总是在堆栈流中进行大量迭代,例如main = print $ iterate f 0.3 !! 1000000

1 个答案:

答案 0 :(得分:24)

问题在于您的定义

iter :: Int -> Double
iter 0 = 0.3
iter n = f $ iter (n-1)

尝试以错误的方向进行评估。展开它几步,我们获得

iter n = f (iter (n-1))
       = f (f (iter (n-2)))
       = f (f (f (iter (n-3))))
       ...

并且必须在可以评估任何内容之前构建从iter 1000000iter 0的整个调用堆栈。用严格的语言也是一样的。您必须对其进行组织,以便在重复之前进行部分评估。通常的方法是有一个累积参数,比如

iter n = go n 0.3
  where
    go 0 x = x
    go k x = go (k-1) (f x)

然后添加严格注释 - 如果编译器尚未添加它们 - 将使其平稳运行而不消耗堆栈。

iterate变体与iter存在同样的问题,只有调用堆栈是由内而外构建的,而不是由你自己构建的。但是由于iterate从内到外构建了它的调用堆栈,更严格的iterate版本(或之前强制执行早期迭代的消费模式)解决了这个问题,

iterate' :: (a -> a) -> a -> [a]
iterate' f x = x `seq` (x : iterate' f (f x))

无问题地计算iterate' f 0.3 !! 1000000