作为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多次迭代函数的方法是什么?
我已尝试过相关帖子中的建议:here或here,但我总是在堆栈流中进行大量迭代,例如main = print $ iterate f 0.3 !! 1000000
。
答案 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 1000000
到iter 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
。