了解惰性评估的局限性(Eratosthenes 筛法)

Understanding the Limitations of Lazy Evaluation (Sieve of Eratosthenes)

Haskell Wiki article on prime numbers 中,描述了埃拉托色尼筛法的以下实现:

primes = 2 : 3 : minus [5,7..] (unionAll [[p*p, p*p+2*p..] | p <- tail primes])

做的时候...

primes !! 2

...Haskell 如何在这种特定情况下识别不尝试 primes 尾部的所有 p(a.k.a [3..]), 但只用 3 做一个集合减去?

换句话说:Haskell 怎么知道其他任何 p(或其倍数)将不匹配 5,这是最终的答案。是否有一个很好的经验法则来了解编译器何时足够智能以处理无限情况?

(!!)只要求对primes进行足够的评估,找出至少有3个元素,以及第三个元素是什么。要获得第三个元素,我们需要开始评估对 minus.

的调用

minus 假定它的两个参数都已排序。这在文档中有描述,并且在 primes 的定义中得到满足。 minus 执行的第一个比较是在 5 和 9 之间,这表明 5 是结果的第一个元素。在definition of minus中是这样的LT -> x : loop xs (y:ys).

(!!) 现在有 primes 的第三个元素,因此计算不会在 primesminusunionAll 中继续。这种在子表达式求值和外部表达式中的模式匹配之间的来回反复是惰性求值。

其实症结在执行unionAllminus 只是在不知不觉中从它的正确参数中一个一个地提取元素(当然它假设它的两个参数都是非递减的)。

首先,我们重写为

primes = 2 : ps 
ps     = 3 : t
t      = minus [5,7..] (unionAll [[p*p, p*p+2*p..] | p <- ps])

-- primes !! 2 == ps !! 1 == head t

       = minus [5,7..] (unionAll [[p*p, p*p+2*p..] | p <- (3 : t)])
       = minus [5,7..] (unionAll ([9, 15..] : [[p*p, p*p+2*p..] | p <- t]))

现在 unionAll 很聪明:它依赖于 unionAll xs 中的假设(这里是事实),它认为 (map head xs) 是非递减的。

因此,它知道不必与任何东西进行比较 9!所以它只是无条件地产生它(你可以参考它的定义自己检查它):

       = minus [5,7..] 
               (9 : union [15, 21..] (unionAll ........))

因此 minus 有一些东西可以与 57 进行比较,并产生:

       = 5 : 7 : minus [9,11..] 
                       (9 : union [15, 21..] (unionAll ........))

所有这些都来自于只知道第一个奇素数,3