Clojure's lazy-seq
I’m reading through Programming Clojure and was struck by one of their examples on lazy-seq. They didn’t go into detail because they will be going more in detail on lazy evaluation on a future chapter. The started to analyze the code:
A few things of note:
- This is not a function definition. This is binding
primes
to a list which is lazy evaluated. - The function
primes-from
is named twice. Once for thelet
to get local scoping and as the name argument forfn
so that this function has a name. The name is required in this case because it is called recursively. - The
if
condition checks to see if n is divisible by any of the previous subset of primes. It is not necessary to check the complete previously generated primes because mathematically only numbers \\( \sqrt{n} \\) is a possible divisors of \\(n\\). - In
take-while
in theif
conditional, it is necessary to find a value to test that is included in the previous cached values ofprimes
. For instance, if you had
This would never end (infinite recursion) because let’s say n = 8. primes
would return [2 3 5 7]
, which are all less than 8 so it will try to fetch the
next prime number from primes
, which is exactly what it was trying to do in
the first place. Thus it gets caught in an infinite recursion. You need the
test to have a failure point (ie where it evaluates to false) within the
precomputed list. So in the original code (take-while #(< (* % %) n) primes
,
3 does the trick since (< 9 n)
(n=8 in this case) stops the take-while
from continuing.
(recur (+ n f) r)
is allowed to be used instead of a normal recursive call because it is the last line of executable code of the functionprimes-from
.recur
works just like a recursive call without building a new stack – for efficiency.- The
recur
in theif
clause don’t havelazy-seq
because we haven’t found a prime and we want it to keep searching. - The else clause needs a
lazy-seq
because it found the next prime and wants to have that value available and we do not want to have the recursive call toprime-from
executed here but at the time when it gets evaluated. In effect it is putting a marker here to say what it needs to execute when it the evaluator sees it. - The recursive call in the else clause cannot use
recur
because there is code that needs to be executed afterprime-from
returns with it’s value. - The following code could be written another way from
to
- Seems like
lazy-seq
needs to have a recursive call in the body otherwise it throws an exception. - The
wheel
is just a set of increments used to find the next number to test for a prime. Since we know that 2, 3, 5, 7 are prime, we can skip multiples of these. Once you get the list of offsets, they cycle.