1

I found this solution on the internet and I need some help understanding it:

isPrime' :: Integer -> Bool
isPrime' n = foldr (\x acc -> (n `rem` x) /= 0 && acc) True primes
    where primes = 2 : filter isPrime' [3,5..]

A couple of things:

My understanding is that if the accumulator for a fold function is a Boolean it has to be set in the lambda function itself. Something like:

(\x acc -> if (n `rem` x /= 0) then False else acc) True primes

But here it is not the case.

Also, the range being used for primes does not have a terminating number. I know the reason this works is because of Haskell's lazy evaluation, but how exactly is that working here?

Lastly, this function does not seem like it would return the proper Boolean. A prime number is one that has no other divisors but itself and 1. So shouldnt the lambda read:

(\x acc -> if (n `rem` x) == 0 then False else acc) True primes

I am thoroughly confused. Please help me out.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
dopatraman
  • 13,416
  • 29
  • 90
  • 154
  • ``(n `rem` x) /= 0 && acc`` is equivalent to ``if (n `rem` x) /= 0 then acc else False`` or ``if (n `rem` x) == 0 then False else acc``. – Will Ness Aug 04 '15 at 23:00

3 Answers3

1

At least for me, your code doesn't finish. It is missing the stop condition, which can be seen in a similar solution from the wiki:

isPrime n = n > 1 &&
              foldr (\p r -> p*p > n || ((n `rem` p) /= 0 && r)) True primes

primes = 2 : filter isPrime [3,5..]

Here you can see, that the condition p*p > n produces True, when there is no possible prime factor left. Because of lazy execution the right part of || is not evaluated and the foldr stops.

Stefan
  • 2,460
  • 1
  • 17
  • 33
1

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 ns 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 ns 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).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
0

The expression

(n `rem` x) /= 0

clearly produces a Bool result. The parameter acc is already a Bool value. So

(n `rem` x) /= 0 && acc

is a logical-AND of two Bool values, which clearly produces a Bool result. Where is the confusion?

The input list is infinite, consisting only of odd numbers. So long as you can produce a result after examining only a finite number of values, laziness makes everything fine.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • Where is the result produced? Even if the lambda evaluates to false doesnt the fold continue? – dopatraman Aug 04 '15 at 16:46
  • 2
    @dopatraman The definition of `&&` is `True && x = x; False && _ = False`. So `False && acc` is `False` regardless of `acc`'s value and `acc` is not evaluated in that case (and if `acc` is not evaluated, the `fold` does not continue as the recursive call to `foldr` is the value of `acc` and thus it is only evaluated when `acc` is). – sepp2k Aug 04 '15 at 17:08
  • `take 2 primes` loops, yes? – Will Ness Aug 05 '15 at 14:05