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

时间:2018-09-20 23:23:53

标签: haskell functional-programming lazy-evaluation ghc sieve-of-eratosthenes

Haskell Wiki article on prime numbers中,描述了Eratosthenes筛网的以下实现:

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

在做...

primes !! 2

... Haskell如何在这种特定情况下认识到不要尝试p(又名primes)尾部的所有[3..],而是只减去一组与3

换句话说:Haskell如何知道其他p的任何一个(或其倍数)将不匹配5,这是最终的答案。有什么好的经验法则可以知道编译器何时足够聪明,可以处理无限情况?<​​/ p>

2 个答案:

答案 0 :(得分:4)

(!!)仅要求对primes进行足够的评估才能发现至少有3个元素,第三个元素是什么。要获得第三个元素,我们需要开始评估对minus的调用。

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

(!!)现在具有primes的第三个元素,因此评估不会在primesminusunionAll中继续进行。子表达式的评估与外部表达式中的模式匹配之间的这种来回交互是懒惰的评估。

答案 1 :(得分:2)

实际上,关键在于unionAll的实现。 minus只是不知不觉地从其正确的参数中拉出了元素(它假定两个参数当然都没有减少)。

首先,让我们将其重写为

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