如何一次评估惰性列表?

时间:2019-04-09 15:06:43

标签: haskell

I referred to this post来计算函数nthPrimes n的列表并返回第n个素数的列表:

import qualified Data.Set as PQ

main :: IO ()
main = print $ nthPrimes ns
  where
    ns = [1,3,10]

nthPrimes :: [Int] -> [Integer]
nthPrimes = map (primes !!)

primes :: [Integer]
primes = 2:sieve [3,5..]
  where
    sieve (x:xs) = x : sieve' xs (insertprime x xs PQ.empty)

    sieve' (x:xs) table
        | nextComposite == x = sieve' xs (adjust x table)
        | otherwise          = x : sieve' xs (insertprime x xs table)
      where
        (nextComposite,_) = PQ.findMin table

    adjust x table
        | n == x    = adjust x (PQ.insert (n', ns) newPQ)
        | otherwise = table
      where
        Just ((n, n':ns), newPQ) = PQ.minView table

    insertprime p xs = PQ.insert (p*p, map (*p) xs)

因此,这将打印[3,7,31]

但是,由于primes函数是惰性的,因此它将在每次调用中一次又一次地求值以获得n素数,但是实际上,如果我们知道{{ 1}}有一个最大限制(例如n),因为1000永不更改,并且计算primes占用大量CPU和内存。

所以问题是如何对前X个元素强制执行primes的求值,以便所有预先求值的元素都可以在从中获取的函数中重用?

我相信重用预先评估的元素会减少整体内存使用量,尤其是当primes是一个很长的列表时,对吗?

1 个答案:

答案 0 :(得分:5)

正如@chepner在注释中指出的那样,primes是一个列表,而不是一个函数。 Haskell的工作方式是根据需要计算primes列表中的元素,但是一旦计算,它们就会保存在内存中,并且不会一遍又一遍地重新计算。

您可以通过将模块加载到GHCi中来自己查看:

> :l MyPrimes
> :set +s
> nthPrimes [100000]
[1299721]
(6.45 secs, 3,246,628,344 bytes)
> nthPrimes [100001]
[1299743]
(0.01 secs, 188,184 bytes)
>

计算第100,000个素数大约需要6秒钟。一旦完成,计算第100,001个素数仅需0.01秒。