Here is an alternative way to generate primes using a sieve.
It is not particularly efficient, but it demonstrates lazy streams.
A stream is kind of like a list, except the tail is represented with a function that hasn't been evaluated. The particular definition of a stream below doesn't have a way to represent "end of stream", which strict lists necessarily have to. But since the primes are infinite, a stream that holds them can conceptually be, too.
datatype 'a stream = Cons of 'a * (unit -> 'a stream)
The primes are a subset of the natural numbers,
fun nats i =
Cons (i, fn () => nats (i+1))
fun take 0 _ = []
| take 1 (Cons (x, _)) = [x]
| take n (Cons (x, stream)) = x :: take (n-1) (stream ())
- take 10 (nats 0);
> val it = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] : int list
As for picking the primes using a sieve, see the animation of Sieve of Eratosthenes (Wikipedia): We pick the first element (starting from 2) and cross out subsequent multiples (4, 6, 8, ...). We pick the next element (being 3) and cross out subsequent multiples (6, 9, 12, ...). We pick the next element (5, since 4 is crossed out) and cross out subsequent multiples (10, 15, 20, ...), and so on.
A recursive solution using lazy streams might then look like
fun sieve (Cons (n, stream)) =
Cons (n, fn () => sieve (remove_multiples n (stream ())))
and remove_multiples i =
filter (fn n => n mod i <> 0)
and filter p (Cons (x, stream)) =
if p x
then Cons (x, fn () => filter p (stream ()))
else filter p (stream ())
Here sieve
is a recursive, lazy definition because it calls itself inside an fn () => ...
which delays computation. It progressively filters out more multiples because it doesn't just call itself with a stream that has one element removed, but a stream that has a multitude of elements removed upon each recursive call.
You can take the first 10 primes:
fun primes n =
take n (sieve (nats 2))
- primes 10;
> val it = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] : int list
Or you can take the first primes for which a predicate holds:
fun takeWhile p (Cons (x, stream)) =
if p x then x :: takeWhile p (stream ()) else []
fun primesUpto n =
takeWhile (fn p => p <= n) (sieve (nats 2))
- primesUpto 100;
> val it =
[2, 3, 5, 7, 11,
13, 17, 19, 23,
29, 31, 37, 41,
43, 47, 53, 59,
61, 67, 71, 73,
79, 83, 89, 97] : int list
If you'd like a more efficient but still functional and lazy strategy, have a look at the wheel sieve.