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

作为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迭代函数多次的方法是什么


我尝试过相关帖子的建议:或者,但我总是在大量迭代中使用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]