【问题标题】:Understanding the Limitations of Lazy Evaluation (Sieve of Eratosthenes)了解惰性评估的局限性(埃拉托色尼筛法)
【发布时间】:2019-02-25 07:48:03
【问题描述】:

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(又名[3..])尾部的所有p,而是只用3 减去一个集合?

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

【问题讨论】:

  • Haskell 自己不知道。但minus 的实现可能。鉴于在该代码片段中我们有 minusunionunionAll 来自 Data.List.Ordered,这意味着在某些时候,您使用的无限列表是有序的这一事实用于缩短计算.运行时将保证,一旦它知道列表的第三个元素(即,它知道列表的形式为a : b : c : rest),它将停止计算列表的其余部分。

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


【解决方案1】:

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

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

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

【讨论】:

  • 啊,谢谢你把事情弄清楚了!为了澄清我是否理解正确,这是否意味着 unionAll 能够提前短路计算,因为它知道第一个列表是 least (文档:“假设内部列表的头部是排序”)。我是否也在使用正确的术语来描述这一点?再次感谢您!
【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多