惰性求值在无限素数列表生成器中是如何工作的?HASKELL

bpzcxfmw  于 2023-11-18  发布在  其他
关注(0)|答案(2)|浏览(120)
primes :: [Int]
primes = sieve [2..]

sieve :: [Int] -> [Int]
sieve (n:ns) = n : sieve [n' | n' <- ns, mod n n' /= 0]

字符串
所以我得到了这个,这几行代码将生成一个无穷大的素数列表,但是我很难理解一件事,当我试图从一个无穷大的列表中过滤出所有2的倍数时,这些倍数也是无穷大的,所以我们在过滤掉2的所有倍数之前是如何进行递归调用的。因为我试图生成一个所有素数的无限列表,所以所有2的倍数需要先被去掉,然后再去找下一个质数?

wgeznvg7

wgeznvg71#

filter,像Haskell中的许多东西一样,是懒惰的。它也不会立即评估其结果。即使给定一个有限列表,它也只是“记住”它需要做什么,并且只有在被迫时才这样做。让我们看看评估的几个步骤。这是你的代码

sieve :: [Int] -> [Int]
sieve (n:ns) = n : sieve [n' | n' <- ns, mod n' n /= 0]

字符串
(Note:在您的版本中,n'nmod中是向后的;我已经冒昧地纠正了这一点)
让我们假设我刚刚完成了print (sieve [2..])(其中print为了打印的目的而完全强制其参数)。
然后我们有

sieve [2..]


sieve在其参数上进行模式匹配,因此我们需要无限参数的第一个元素。也就是说,我们必须强制第一个元素,以获得

sieve (2 : [3..])


现在我们有足够的信息来调用sieve一次。让我们用右边的定义替换左边的定义。注意,n2ns[3..]

2 : sieve [n' | n' <- [3..], mod 2 n' /= 0]


在这一点上,我们还没有强制列表解析。它仍然只是一个美化的过滤器。在这一点上,print有足够的信息来打印出2。但是现在我们需要列表的下一个元素,因为我们要把它们都打印出来。所以我们需要对sieve进行足够远的计算,以获得下一个元素。sieve,反过来,模式匹配它的参数,所以我们需要列表解析的第一个元素。

-- Grab the first possible value of n'
2 : sieve [n' | n' <- 3 : [4..], mod n' 2 /= 0]
-- Oh look! `mod 3 2 /= 0` is true! We can include this!
2 : sieve (3 : [n' | n' <- [4..], mod n' 2 /= 0])


现在sieve计算,n等于3ns等于.

2 : 3 : sieve [n' | n' <- ns, mod n' 3 /= 0]


但正如我所说的,ns * 实际上 * 是整个列表的理解,所以这实际上是

2 : 3 : sieve [n' | n' <- [n' | n' <- [4..], mod n' 2 /= 0], mod n' 3 /= 0]


好了,现在让我们再看一步,看看跳过一个值是什么样子的。和前面一样的规则:我们需要另一个值,所以sieve需要产生一些东西,但是它需要参数的第一个元素来进行模式匹配。所以我们试图将一个元素从列表解析中强制出来。

-- Original value (same as above)
2 : 3 : sieve [n' | n' <- [n' | n' <- [4..], mod n' 2 /= 0], mod n' 3 /= 0]
-- Now let's try to get a value from the list comprehension
2 : 3 : sieve [n' | n' <- [n' | n' <- 4 : [5..], mod n' 2 /= 0], mod n' 3 /= 0]
-- Uh-oh! `mod 4 2 /= 0` is false; throw this value away!
2 : 3 : sieve [n' | n' <- [n' | n' <- [5..], mod n' 2 /= 0], mod n' 3 /= 0]
-- I still need a value; keep going
2 : 3 : sieve [n' | n' <- [n' | n' <- 5 : [6..], mod n' 2 /= 0], mod n' 3 /= 0]
-- `mod 5 2 /= 0` is true, so the inner comprehension succeeds.
2 : 3 : sieve [n' | n' <- (5 : [n' | n' <- [6..], mod n' 2 /= 0]), mod n' 3 /= 0]
-- `mod 5 3 /= 0` is true, so the outer one succeeds.
2 : 3 : sieve (5 : [n' | n' <- [n' | n' <- [6..], mod n' 2 /= 0], mod n' 3 /= 0])


现在sieve有足够的信息来继续,所以它根据自己的定义生成5的值,然后用一个新的参数调用自己,这个参数将由 * 三个 * 嵌套的列表解析组成。解析将继续增长。
实际上,任何Haskell实现都会优化它,所以你可能最终会得到一个有多个条件的大列表解析,但这是一个优化技巧,不是非严格语义本身的一部分。

h7wcgrx3

h7wcgrx32#

所以所有2的倍数在我们找到下一个质数之前都要去掉?

,过滤也是懒惰的。实际上,过滤操作,也就是你的列表解析所做的,是这样实现的:

filter :: (a -> Bool) -> [a] -> [a]
filter p = go
  where go [] = []
        go (x:xs) | p x = x : go xs
                  | otherwise = go xs

字符串
这也可以作为一个生成器:你可以问下一个项目,它会返回下一个不能被2,3,5等整除的项目。
因此,列表解析并不急于首先过滤掉所有的项。它传递一个生成器,该生成器将处理过滤后的项。因此,这意味着如果我们传递一个无限列表[2,3,4,.] , it will return 2`作为第一项,并递归一个不能被2整除的所有项的列表。但是这个列表也是懒惰的。如果我们然后要求下一项,它将返回3,我们在一个生成器上递归,该生成器将另一个生成器作为输入,并将返回除以3的项。

相关问题