【问题标题】:Haskell: repeat a function a large number of times without stackoverflowHaskell:在没有stackoverflow的情况下大量重复一个函数
【发布时间】:2012-02-13 03:56:33
【问题描述】:

作为 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 $ iterate f 0.3 !! 1000000

【问题讨论】:

  • 问题是你没有尾递归,因为你没有直接返回iter (n-1)
  • 有趣的是,人们只是不明白尾递归是什么。仅供参考,这个定义是错误的:“当我们所在的函数的名称出现在该函数的最后一行时”。

标签: haskell stack-overflow


【解决方案1】:

问题在于你的定义

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没有问题。

【讨论】:

  • 换句话说,iter 的原始定义不是尾递归的。
  • 这是学习折叠的好时机! iter n = foldl' (\acc f -> f acc) 0.3 (replicate n f)
  • @JohnL 对,但是尾递归在 Haskell 中并不是 重要的,一方面,由于惰性/非严格性,尾递归函数仍然会导致堆栈溢出通过构建大型 thunk,因此也必须确保适当的严格度/热切度。另一方面,如果递归调用在正确的位置,则非尾递归没有堆栈溢出问题,例如一个惰性构造函数字段(受保护的递归是这里的重要概念)。
  • 最后一行应该是iterate' f 0.3 !! 1000000
  • @rampion 为什么不iter n = foldl' (\acc _ -> f acc) 0.3 [1..n]
猜你喜欢
  • 2017-06-26
  • 1970-01-01
  • 2011-12-15
  • 2018-03-24
  • 1970-01-01
  • 2011-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多