有
primes = [p1, p2, p3, ..., pn, ...]
计算
isPrime' n = foldr (\x acc -> (n `rem` x) /= 0 && acc) True primes
仿佛在计算
isPrime' n = rem n p1 /= 0 && (
rem n p2 /= 0 && (
rem n p3 /= 0 && (
..........
rem n pk /= 0 && (
.......... ..... ))))
对于复合n
s,这是可行的——其中一个rem
表达式将为 0,不等式为假,整个表达式也为假,因为&&
它是短路的,在其第二个参数中是惰性的。
对于素数n
s,这将导致一个问题:根据rem
素数的定义,任何表达式都不会是 0,直到我们p_i == n
在素数中找到素数本身。但它还不存在,因为我们还没有检测到它是素数——我们现在就在做。要测试它是否是素数,我们必须知道它是素数- 显然是一个糟糕的情况。
将要求primes
一个尚不存在的值。这被称为“黑洞”。它会导致错误,并且计算中止(或卡住)。
换一种说法,就好像定义
primes = 2 : filter isPrime' [3,5..]
被定义为
primes = 2 : [ n | n <- [3,5..]
, and [rem n p /= 0 | p <- takeWhile (< n) primes]]
问题是要停止何时n
是素数,takeWhile (< n)
必须达到p
高于或等于n
in的素数primes
,但它还没有。“黑洞”。
著名的“筛子”代码通过“转置”工作流程来解决这个问题,
primes = map head . iterate (\(x:xs)-> filter ((/=0).(`rem`x)) xs) $ [2..]
从而使其工作,同时保持其计算效率低下(代码的较小问题),不必要地用太多素数测试其候选者(其中每个素数都由其所有前面的素数测试,而不仅仅是那些不高于其平方根的素数),使其不必要的慢。
这可以通过适当提前停止测试来缓解,
primes = 2 : [ n | n <- [3,5..]
, and [rem n p /= 0 | p <- takeWhile ((<= n).(^2)) primes]]
根据您的定义,这相当于
isPrime' n = p1*p1 > n || rem n p1 /= 0 && (
p2*p2 > n || rem n p2 /= 0 && (
p3*p3 > n || rem n p3 /= 0 && (
..........
pk*pk > n || rem n pk /= 0 && (
.......... ..... ))))
您foldr
现在可以将其写为基于 - 的定义(可以在此处的另一个答案中看到)。