Here is an example:
;; Helper function for marking multiples of a number as 0
(def mark (fn [[x & xs] k m]
(if (= k m)
(cons 0 (mark xs 1 m))
(cons x (mark xs (inc k) m))
)))
;; Sieve of Eratosthenes
(defn sieve
[x & xs]
(if (= x 0)
(sieve xs)
(cons x (sieve (mark xs 1 x)))
))
(take 10 (lazy-seq (sieve (iterate inc 2))))
It produces a StackOverflowError.
There are a couple of issues here. First, as pointed out in the other answer, your
mark
andsieve
functions don't have terminating conditions. It looks like they are designed to work with infinite sequences, but if you passed a finite-length sequence they'd keep going off the end.The deeper problem here is that it looks like you're trying to have a function create a lazy infinite sequence by recursively calling itself. However,
cons
is not lazy in any way; it is a pure function call, so the recursive calls tomark
andsieve
are invoked immediately. Wrapping the outer-most call tosieve
inlazy-seq
only serves to defer the initial call; it does not make the entire sequence lazy. Instead, each call tocons
must be wrapped in its own lazy sequence.For instance:
Compare this with the actual source code of
iterate
:Putting it together, here's an implementation of
mark
that works correctly for finite sequences and preserves laziness for infinite sequences. Fixingsieve
is left as an exercise for the reader.Thanks to @Alex's answer I managed to come up with a working lazy solution:
I was adviced by someone else to use
rest
instead ofnext
.Need terminating conditions
The problem here is both your
mark
andsieve
functions have no terminating conditions. There must be some set of inputs for which each function does not call itself, but returns an answer. Additionally, every set of (valid) inputs to these functions should eventually resolve to a non-recursive return value.But even if you get it right...
I'll add that even if you succeed in creating the correct terminating conditions, there is still the possibility of having a stack overflow if the depth of the recursion in too large. This can be mitigated to some extent by increasing the JVM stack size, but this has it's limits.
A way around this for some functions is to use tail call optimization. Some recursive functions are tail recursive, meaning that all recursive calls to the function being defined within it's definition are in the tail call position (are the final function called in the definition body). For example, in your
sieve
function's(= x 0)
case,sieve
is the tail call, since the result ofsieve
doesn't get passed into any other function. However, in the case that(not (= x 0))
, the result of callingsieve
gets passed tocons
, so this is not a tail call. When a function is fully tail recursive, it is possible to behind the scenes transform the function definition into a looping construct which avoids consuming the stack. In clojure this is possible by usingrecur
in the function definition instead of the function name (there is also aloop
construct which can sometimes be helpful). Again, because not all recursive functions are tail recursive, this isn't a panacea. But when they are it's good to know that you can do this.