【问题标题】:How does Haskell evaluate this primes function?Haskell 如何评估这个素数函数?
【发布时间】:2018-12-06 03:39:07
【问题描述】:

我发现很难理解 Haskell 将如何评估这个 primes 函数。 primes 函数是否被反复评估,或者primeFactors 函数中的primes 将指向第一个primes

primes = 2 : filter ((==1) . length . primeFactors) [3,5..]

primeFactors n = factor n primes
  where
    factor n (p:ps)
        | p * p > n        = [n]
        | n `mod` p == 0   = p : factor (n `div` p) (p:ps)
        | otherwise        = factor n ps

main :: IO ()
main = print $ length . take 100000 $ primes

【问题讨论】:

  • primes 根本不是函数;这只是一个列表。
  • 谢谢。所以在幕后,现在每次调用 primeFactors 时,primes 都与我们上次调用 primeFactors 时不同。这是partially evaluating
  • @dandoh primes 永远不会改变,就像 Haskell 中的所有值一样。 primes 只是自我引用——它就像无限列表x = 1 : x,它是1s 的无限列表。
  • 离题了,但是检查一个非空列表是否只有一个元素可以用null . tail而不是(== 1) . length来完成。这样primeFactors 将在找到第二个因子后停止计算因子,而不是计算所有因子。请注意,这不适用于空列表(此处不是这种情况)。
  • 这样的定义被称为constant applicative forms

标签: haskell lazy-evaluation weak-head-normal-form


【解决方案1】:

primes 只是一个列表。它的第一个元素是 2,其余元素取自(部分)由函数 primeFactors 过滤的奇数列表。

primeFactors 使用 primes。这不是循环吗?

不完全是。因为 Haskell 是懒惰的,primeFactors 不需要同时需要 primes 中的所有值,只需要小于或等于其参数平方根的值(p:ps 匹配 primes,但我们只需要ps 如果p*p <= n),并且这些素数都是通过先前对primeFactors 的调用找到的。

例如,跟踪对primeFactors 的前几次调用。为简洁起见,让b = (==1) . length . primeFactors

primeFactors 3 == factor 3 primes
               -- only unpack as much of primes as we need for the next step
               == factor 3 (2:filter b [3,5..])
               -- because 2*2 > 3, that's only one level
               == [3]

因此,由于b [3] 为真,我们知道3 是primes 的下一个元素。即primes = 2:3:filter b [5,7..]

primeFactors 5 == factor 5 primes
               == factor 5 (2:3:filter b [3,5..])
               -- 2*2 > 5 is false, as is 5 `mod` 2 == 0, so
               == factor 5 (3:filter b [3,5..])
               -- 3*3 > 5, so
               == [5]

b [5] 为真,所以5primes 的下一个元素。

primeFactors 7 == factor 7 primes
               == factor 7 (2:3:5:filter b [3,5..])
               == factor 7 (3:5:filter b [3,5..])
               -- 3*3 > 7
               == [7]

b [7] 为真,所以7primes 的下一个元素。 (似乎所有内容都添加到了primes,不是吗?再拨打primeFactors 将表明情况并非如此)

primeFactors 9 == factor 9 primes
               == factor 9 (2:3:5:7:filter b [3,5..])
               -- 2*2 > 9 and 9 `mod` 2 == 0 are false
               == factor 9 (3:5:7:filter b [3,5..])
               -- 3*3 > 9 is false, but 9 `mod` 3 == 0 is true, so
               == 3 : factor (9 `div` 3) (3:5:7:filter b [3,5..])
               == 3 : factor 3 (3:5:7:filter b [3,5..])
               -- 3*3 > 3 is false, but 3 `mod` 3 == 0, so
               == 3 : [3] == [3,3]

但由于b [3,3] 为假,9 不是primes 的元素。所以现在我们有

 primes = 2:3:5:7:filter b [3,5..])

这是一个漫长而乏味的过程,但你应该感觉到primes总是保持在primeFactors的“领先”; primeFactors 需要的 primes 元素始终由对 primeFactors 的早期调用确定。

【讨论】:

  • 如果我错了,请纠正我。所以对内来说primes总是指向同一个内存块,会逐渐被求值?
  • 基本上是的。
  • 同样概念的一个更简单的例子是无限列表,ones = 1:oneshead ones == 1tail ones = oneshead (tail ones) 是什么?好吧,它是head (tail (1:ones)) == head ones == head (1:ones) == 1(只有在绝对必要时才将ones 的定义替换为1:ones)。
【解决方案2】:

Haskell 将如何评估这个素数函数?

正如您的代码所示,它打印出前 100000 个素数,那么primes 如何工作?

首先,生成第一个素数,很简单,就是列表的第一个元素:

2 : filter ((==1) ...

2,对于下一个,我们需要应用primeFactors函数作为

primeFactors 3 = factor 3 primes

现在可能会让刚接触 Haskell 的人感到困惑,如何评估上述表达式中的 primes?答案是,它只是一个元素为[2,...] 的列表,由于惰性评估,现在我们不需要评估primes 函数生成的列表的所有素数。我们只需要评估下一个,然后看看会发生什么。所以,我们得到2,上面的表达式就变成了:

 primeFactors 3 = factor 3 [2,..]

factor 3 (2:ps) | 2 * 2 > 3 = [3]

所以,primeFactors 3 重跑[3]

所以

2: filter ((==1) . length . primeFactors) 3 = [2,3]

我们现在成功生成了 2 个质数,但是我们需要 100000,那么接下来呢?显然,我们将5 应用于以下表达式:

2: filter ((==1) . length . primeFactors) 5

重复上述过程:

primeFactors 5 = factor 5 [2,3,..]

这次我们在列表中有 2 个元素:

factor 5 [2,3..]

factor 5 [2,3..] | otherwise = factor 5 [3,...]

factor 5 [3,...] | 3 * 3 > 5 = [5]

不断重复,直到生成 100000 个素数,再一次,由于惰性求值,我们不需要 100001 个素数,所以计算停止并打印出结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-07
    • 1970-01-01
    • 2015-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多