Haskell:在没有堆栈溢出的情况下多次重复函数
作为Haskell的新手,我尝试多次迭代一个函数(例如逻辑映射)。在命令式语言中,这将是一个简单的循环,但是在Haskell中,我最终会导致堆栈溢出。以该代码为例:Haskell:在没有堆栈溢出的情况下多次重复函数,haskell,stack-overflow,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 overf
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迭代函数多次的方法是什么
我尝试过相关帖子的建议:或者,但我总是在大量迭代中使用stackoverflow,例如,main=print$iterate f0.3!!1000000
问题在于您的定义
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))
计算
迭代'f0.3!!1000000
没有问题。问题是你没有尾部递归,因为你没有直接返回iter(n-1)
有趣的是,人们根本不知道尾部递归是什么。仅供参考,这个定义是错误的:“当我们刚刚使用的函数的名称出现在该函数的最后一行时”。换句话说,iter
的原始定义不是尾部递归的。这使得这是学习折叠的好时机iter n=foldl'(\acc f->f acc)0.3(replicate n f)
@JohnL Right,但尾部递归在Haskell中并不重要,一方面,由于惰性/非严格性,尾部递归函数仍然可以通过构建大thunk导致堆栈溢出,因此必须确保适当的严格性/迫切性。另一方面,如果递归调用位于正确的位置,例如惰性构造函数字段(保护递归是这里的重要概念),则非尾部递归没有堆栈溢出问题。最后一行应该是iterate'f 0.3!!1000000
@rampion为什么不iter n=foldl'(\acc\uf->acc)0.3[1..n]
?