我刚刚开始学习Haskell,并将阅读书籍和教程与Project Euler的解决问题相结合。我一直坚持下去 问题27 因为我使用此代码会遇到“ C堆栈溢出”错误:

Euler.hs

divisors n = [x | x <- [1..n `div` 2], n `mod` x == 0] ++ [n]
is_prime n = divisors n == [1, n]
f a b = [n^2 + a * n + b | n <- [0..]]
primes_from_zero a b = length(takeWhile is_prime (f a b))

命令窗口

该命令给出了Euler的系数1和41(连续40个素数)

foldr (max) (0, 0, 0) [(primes_from_zero a b, a, b) | a <- [0..10], b <- [0..50]]

这是“ C堆叠溢出”失败的(我想获得问题定义中也提到的系数-79和1601):

foldr (max) (0, 0, 0) [(primes_from_zero a b, a, b) | a <- [-100..0], b <- [1500..1700]]

您能告诉我,为什么出现错误以及如何解决?谢谢!

我使用winhugs。

有帮助吗?

解决方案

“堆栈溢出”错误意味着您程序中的函数链条调用(从输入函数到当前执行函数)变得太大了。大多数编译器和运行时间将呼叫链作为堆栈数据结构实现 - 每个元素是包含局部变量和单个函数调用的上下文的“堆栈帧”,其大小有限。

通常,堆栈溢出意味着递归功能有问题。例如,如果递归永不终止,它将最终达到堆栈限制和“溢出”。即使递归正在终止,如果呼叫太多,它也可能溢出。通常,列表非常大,您的示例似乎就是这种情况。

避免在Haskell(以及许多其他语言)中堆叠溢出的一种方法是写 尾部回复功能. 。尾部回复功能是唯一的递归调用是该函数的结果。例如,

foldl f x (y:ys) = foldl f (f x y) ys

相比之下, foldr 不是尾部递归

foldr f x (y:ys) = f y (foldr f x ys)

出于技术原因,尾递归电话可以重新使用呼叫者的堆栈框架,因此不会导致调用堆栈增长。

(旁注: foldr 不是尾部递归,而是“懒惰” foldl, ,因为它可能不需要评估整个列表。这可能指导您使用哪些决定。)

即使具有尾部回复功能,您也可能由于 “空间泄漏”. 。例如,在 foldl 每个递归电话都会为 (f x y). foldl 使用恒定的堆栈空间,但o(n)空间用于未评估的呼叫 f. 。为了避免这种严格性,您可以使用 foldl'

foldl' f x (y:ys) = (foldl' f $! f x y) ys

infix操作员的位置 $! 强迫评估。

其他提示

我不知道为什么 litb 将他的答案评为评论而不是答案,因此我在这里复制它,以便人们看到它:

http://www.haskell.org/haskellwiki/stack_overflow

我认为这是正确的答案。但是简短的版本是foldr不是尾部递归。

我将这篇文章标记为社区Wiki,因此我不会从中获得任何声誉。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top