4

There are a dozen of confusion when I use lazySeq.

Question:

(def fib
  (lazy-seq
    (concat [0 1] (map + fib (rest fib))))) ;; It's Ok
(take 10 fib) ;; Bomb

Got the error message: StackOverflowError clojure.lang.RT.more (RT.java:589)

And the following solutions works well:

(def fib
  (concat [0 1] (lazy-seq (map + fib (rest fib))))) ;; Works well

(def fib
  (lazy-cat [0 1] (map + fib (rest fib)))) ;; Works well

Both concat and map return lazy sequence, why the above programs look like each other but distinguish?

More detailedly, Why the first example (lazy-seq wrapping the concat) fail but its following example (lazy-seq wrapping the map) sucess?

Liao Pengyu
  • 591
  • 3
  • 12
  • possible duplicate of [Clojure lazy sequence usage](http://stackoverflow.com/questions/4992298/clojure-lazy-sequence-usage) – sloth Apr 30 '13 at 08:19
  • @DominicKexel, actually not the same, my question is focus on the order (or the place) of the `lazy-seq`. In the first example `lazy-seq` locate out of `concat` and bomb in using, but the second `lazy-seq` just wrap the `map` and works well. So I think the order of `lazy-seq` is importante and that is what I want to know, your link does't explain it. – Liao Pengyu Apr 30 '13 at 08:47
  • I'm pretty sure the answer to that question explains that very well. – sloth Apr 30 '13 at 08:50
  • mikera (whos answer was accepted) sayed "it doesn't really matter which order you put them.". But my question shows the order is significant. How do you think the answer explains well? I read the question and the answer and notice that is not the thing i want, have you ever read my question clearly? @DominicKexel – Liao Pengyu Apr 30 '13 at 09:00
  • @mikera Looking forward to your answer. – Liao Pengyu Apr 30 '13 at 09:03
  • @DominicKexel Please look at the first example with patient. I'm not ask for the differences between the cases of using `lazy-seq` and not using. – Liao Pengyu Apr 30 '13 at 09:07

1 Answers1

3

The problem is using rest in map operation. Basically when your lazy-seq will call the expression : (concat [0 1] (map + fib (rest fib))) to return a ISeq object, the rest call on fib will happen (as this is a parameter to map it must be executed first and then passed to map and map is lazy but rest is called before we can reach lazyness). rest will try to call more on the fib which is a LazySeq object and the more will cause the fib lazy seq to get the next ISeq which again involves the execution of whole concat operation which involves rest and it keeps on going this way until stack is blown away.

You need to use something that doesn't call next right away on the fib, something like drop:

(def fib
  (lazy-seq
   (concat [0 1] (map + fib (drop 1 fib)))))

Also, in other case where lazy-seq is inside concat the rest is not executed because it is wrapped inside a lazy-seq operation which make the whole expression a function to be called in future when next ISeq is requested.

Hopefully this clear up things.

Ankur
  • 33,367
  • 2
  • 46
  • 72
  • Wow! That's it! I typed `(take 10 (rest (iterate inc 1)))` and the code works, So I think `rest` is more or less "lazy", but the doc of `rest` did not say it return `lazy sequence`. I think that is the most confusing thing. – Liao Pengyu Apr 30 '13 at 09:51
  • 1
    The ISeq (the base of lazy-ness in clojure) is not similar to your IEnumerable (in CLR) or Iterators (in Java). In those things the next method return the next object and hence they have to maintain a state about the current position whereas ISeq next method return a ISeq (not an object) and hence it doesn't need any state. It is like iterator are : `[1 2 3 4]` , ISeq is `[1 [2 [3 [4 nil]]]]`. This difference is sometimes the reason for confusion – Ankur Apr 30 '13 at 09:58