foldr导致堆栈溢出

时间:2015-03-18 18:15:50

标签: haskell functional-programming

我正试图在无限列表中使用Sieve of Eratosthenes算法生成素数。我听说foldr会懒惰地查看列表,但每次我尝试使用以下算法时都会出现堆栈溢出异常:

getPrimes :: [Int]
getPrimes = foldr getNextPrime [2] [3,5..]
    where
        getNextPrime n primes
            | not $ isAnyDivisibleBy primes n = primes ++ [n]
            | otherwise = primes
        isAnyDivisibleBy primes n = any (\x -> isDivisibleBy n x) primes
        isDivisibleBy x y = x `mod` y == 0

示例:

takeWhile (\x -> x < 10) getPrimes
*** Exception: stack overflow

某个地方正在评估列表,但我无法弄清楚在哪里。

3 个答案:

答案 0 :(得分:3)

我认为foldr让你感到困惑,所以让我们用明确的递归来写出来:

getPrimes :: [Int]
getPrimes = getPrimesUsing [3,5..]

getPrimesUsing :: [Int]->[Int]
getPrimesUsing [] = [2]
getPrimesUsing (n:primes)
  | not $ isAnyDivisibleBy primes n = primes ++ [n]
  | otherwise = primes
  where
    primes = getPrimesUsing primes
    isAnyDivisibleBy primes n = any (\x -> isDivisibleBy n x) primes
    isDivisibleBy x y = x `mod` y == 0

你现在能看到麻烦吗?

一个不相关的观点:你似乎试图在这里实现的算法是不是 Eratosthenes的筛选,而是一种效率低得多的算法,称为试验分割。

答案 1 :(得分:2)

foldr getNextPrime [2] [3, 5 .. ]扩展为:

(getNextPrime 3 (getNextPrime 5 (getNextPrime 7 ...

由于getNextPrime总是需要检查它的第二个参数,我们只得到一个非终止的getNextPrime个调用链,并且从不使用初始的[2]列表。

答案 2 :(得分:2)

foldr定义为

foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)

所以当你插入参数时,你会得到

foldr getNextPrime [2] [3,5..]
    = getNextPrime 3 (foldr getNextPrime [2] [5,7..])
    = getNextPrime 3 (getNextPrime 5 (foldr getNextPrime [2] [7,9..])
    etc...

为了这个懒惰地产生值(处理无限列表时你想要的),getNextPrime需要懒惰地产生值。查看getNextPrimeprimes ++ [n]的定义,意味着您将值附加到primes列表的末尾,但primes的{​​{1}}是{ {1}}。但是getNextPrime 3 getNextPrime 5 (foldr getNextPrime [2] [7,9..])的{​​{1}}是primes等等。您实际上无法为getNextPrime 5生成正常的表单值,它始终是一个永远不会计算的链返回。

另一种看待这种情况的方法是看这是用运算符替换getNextPrime 7 (foldr getNextPrime [2] [9,11..]),让它称之为primes

getNextPrime

(这就是为什么它被称为右折叠,parens嵌套在右边)

这适用于在.:中使用foldr (.:) [2] [3,5..9] = 3 .: (5 .: (7 .: (9 .: [2])))

:

因为foldr只是构建一个新的数据结构,所以可以在不计算结构其余部分的情况下检查该数据结构的第一个元素。但是foldr (:) [2] [3,5..9] = 3 : (5 : (7 : (9 : [2]) 不太好,它首先需要计算:,然后是.:,然后是x1 = 9 .: [2],最后是x2 = 7 .: x1。对于x3 = 5 .: x2而言,您永远无法计算3 .: x3的最后一次调用,但是haskell会继续尝试计算它并且会使堆栈崩溃。