Haskell:多次重复一个函数而不会出现堆栈溢出

发布于 12-27 18:36 字数 883 浏览 1 评论 0原文

作为 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,但我总是以 stackoverflow 进行大量迭代,例如, main = print $ 迭代 f 0.3 !! 1000000。

As a newbie to Haskell I am trying to iterate a function (e.g., the logistic map) a large number of times. In an imperative language this would be a simple loop, however in Haskell I end up with stack overflow. Take for example this code:

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)

For a small number of iterations the code works, but for a million iterations I get a stack space overflow:

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

I cannot understand why this does happen. The tail recursion should be fine here.
Maybe the problem is lazy evaluation. I experimented with several ways to force strict evaluation, by inserting $! or seq at various positions, but with no success.

What would be the Haskell way to iterate a function a huge number of times?

I have tried suggestions from related posts: here or here, but I always ended up with stackoverflow for a large number of iterations, e.g., main = print $ iterate f 0.3 !! 1000000.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

七秒鱼°2025-01-03 18:36:18

问题是你的定义

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))

计算 迭代'f 0.3 !! 1000000 没有问题。

The problem is that your definition

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

tries to evaluate in the wrong direction. Unfolding it for a few steps, we obtain

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

and the entire call stack from iter 1000000 to iter 0 has to be built before anything can be evaluated. It would be the same in a strict language. You have to organise it so that part of the evaluation can take place before recurring. The usual way is to have an accumulation parameter, like

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

Then adding strictness annotations - in case the compiler doesn't already add them - will make it run smoothly without consuming stack.

The iterate variant has the same problem as your iter, only the call stack is built inside-out rather than outside-in as for yours. But since iterate builds its call-stack inside-out, a stricter version of iterate (or a consumption pattern where earlier iterations are forced before) solves the problem,

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

calculates iterate' f 0.3 !! 1000000 without problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文