having
primes = [p1, p2, p3, ..., pn, ...]
calculating
isPrime' n = foldr (\x acc -> (n `rem` x) /= 0 && acc) True primes
is as if calculating
isPrime' n = rem n p1 /= 0 && (
rem n p2 /= 0 && (
rem n p3 /= 0 && (
..........
rem n pk /= 0 && (
.......... ..... ))))
For composite n
s this works – one of the rem
expressions will be 0, the inequality is false, and the whole expression is false too, because &&
is short-circuiting, lazy in its 2nd argument.
For prime n
s this will cause a problem: none of the rem
expressions will be 0, by definition of a prime, until we reach a prime p_i == n
itself among the primes. But it's not there yet, as we haven't detected it as being prime yet - we're just doing it right now. To test whether it is prime, we must know that it is prime - clearly a bad situation.
A value will be demanded from primes
which is not there yet. This is known as "black hole". It causes an error, and calculation aborts (or becomes stuck).
To put it differently, it is as if the definition
primes = 2 : filter isPrime' [3,5..]
were defined as
primes = 2 : [ n | n <- [3,5..]
, and [rem n p /= 0 | p <- takeWhile (< n) primes]]
The problem is that to stop when n
is prime, takeWhile (< n)
must reach a prime p
above or equal to n
in primes
, but it's not there yet. "Black hole".
The famous "sieve" code gets around this problem by "transposing" the workflow,
primes = map head . iterate (\(x:xs)-> filter ((/=0).(`rem`x)) xs) $ [2..]
thus making it work while retaining its computational inefficiency (the lesser problem of your code), testing its candidates by too many primes needlessly (where each prime is tested by all its preceding primes instead of just those not above its square root), making it unnecessarily slow.
This is mitigated by putting a properly early stop to the testing, with
primes = 2 : [ n | n <- [3,5..]
, and [rem n p /= 0 | p <- takeWhile ((<= n).(^2)) primes]]
which, back in terms of your definition, is equivalent to
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 && (
.......... ..... ))))
You can write this down as a foldr
-based definition now (which can be seen in another answer here).