为什么Haskell中的递归函数如此之慢?

Why is this recursive function in Haskell so slow?

我试图模仿筛子,用haskell寻找所有素数小于某个数的素数。我发现了其他的haskell程序,它使用的是快速筛选方法。但是,我编写的以下递归函数非常慢。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
sieve' :: Integer -> Integer -> [Integer]

sieve' n 1 = [2 .. n]

sieve' n (k + 1) | [x | x <- sieve' n k, x == k + 1] == [] = sieve' n k

    |otherwise = [x | x <- sieve' n k,  x == k + 1 || not (mod x (k + 1) == 0)]



sieve :: Integer -> [Integer]

sieve n = sieve' n n

筛子20大约需要2分钟。我写这个问题的时候,30筛还没有完成。

有人能解释一下为什么这个递归函数这么慢吗?感谢您提供的帮助。


sieve'函数的第二个子句是进行两次递归调用(sieve' n k),从而使算法在指数时间内执行。

要解决此问题,可以将术语绑定到某个名称,从而确保对其进行一次评估:

1
2
3
4
sieve' n (k + 1) | [x | x <- rec, x == k + 1] == [] = rec
    |otherwise = [x | x <- rec,  x == k + 1 || not (mod x (k + 1) == 0)]
  where
    rec = sieve' n k

更新以响应询问编译器为什么不自动执行此操作的注释:

这种转换称为CSE(公共子表达式消除),一般来说不是一种优化,而是时间和空间使用之间的权衡,因此最好留给程序员做决定。

谷歌搜索"cse"揭示了一些有趣的讨论,其中一个引用了Simon Peyton Jones 1987年出版的一本书中的一个很好的例子,书名为"函数式编程语言的实现"(哦,天哪,这本书几乎和我一样古老)。