2

Why does this work

(def fibs (cons 0 (cons 1 (lazy-seq (map + fibs (rest fibs))))))
(take 10 fibs)

while this other

(def fibs (lazy-seq (cons 0 (cons 1 (map + fibs (rest fibs))))))
(take 10 fibs)

generate a StackOverflowError?

Matteo
  • 539
  • 3
  • 9
  • What are you trying to achieve with `lazy-seq`? It seems to run fine if you just remove it. – jmargolisvt May 22 '16 at 17:09
  • @jmargolisvt On my repl (clojure 1.6) the code ` (def fibs (cons 0 (cons 1 (map + fibs (rest fibs)))))` does not work. Lazy-seq is necessary I think, because it delays the computation of the stream, otherwise it won't work defining fibs in terms of fibs itself – Matteo May 22 '16 at 17:32

1 Answers1

4

Let's first note that it also works with one cons outside:

(def fibs (cons 0 (lazy-seq (cons 1 (map + fibs (rest fibs))))))

Let's also establish that lazy-seq is a macro (obviously, since it doesn't evaluate the arguments) and puts the body (the argument(s) to lazy-seq) into a function.

Now, if you "request" an element from that sequence that function will be called once and subsequent calls will return the cached value.

Obviously, only once that function returns something you have a value available. Now, what's happening in our bad case:

Imagine clojure is in the middle of evaluating the function for the very first time. The first thing it needs is +, fibs and (rest fibs) in order to pass this to cons. Now clojure will see that fibs is a lazy sequence and invoke it! This is because you're currently in your first invocation and haven't returned. This is causing an infinite recursion.

The fix is easy: Make sure there is a realized element in the beginning of the list so that the two expressions, fibs and (rest fibs) can return something. In particular, taking the rest of (cons 1 whatever) will return whatever without realizing a single element (important since otherwise we'd have the same problem again).

ClojureMostly
  • 4,652
  • 2
  • 22
  • 24