views:

164

answers:

1

When I try to add metadata to an infinite lazy sequence in Clojure, I get a stack overflow, and if I take off the metadata, then it works just fine. Why does adding the with-meta macro break the lazy seq?

First create an infinite seq of a very nice number:

(defn good []
  (lazy-seq 
    (cons 42
      (good))))

user> (take 5 (good))
(42 42 42 42 42)

Then, add some metadata to each of the lazy-seq instances:

(defn bad []
  (lazy-seq 
    (cons 42
      (with-meta 
       (bad)
       {:padding 4}))))


user> (take 5 (bad))
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
  [Thrown class clojure.lang.Compiler$CompilerException]

Try moving the meta data up one level:

(defn also-bad []
  (with-meta 
   (lazy-seq 
     (cons 42
       (also-bad)))
   {:padding 4}))

user> (take 5 (foo))
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
  [Thrown class clojure.lang.Compiler$CompilerException]

Here is an example of meta data on a finite sequence:

(defn also-works []
     (lazy-seq 
       (cons 4 
         (with-meta 
          () 
          {:a 5}))))

user> (also-works)
(4)
user> (meta (rest (also-works)))
{:a 5}
user> 
+4  A: 

Because a LazySeq evaluates its body as soon as you call withMeta on the LazySeq. You lose your laziness.

public final class LazySeq extends Obj implements ISeq, List{
    ...
    public Obj withMeta(IPersistentMap meta){
        return new LazySeq(meta, seq());
    }
    ...
}

seq() evaluates the lazy seq's body if it hasn't already been evaluated. Your code above keeps calling with-meta on successive lazy seqs, which evaluates them all until the stack explodes. I don't think there's currently any way to add metadata to a lazy seq without causing it to evaluate its body.

Brian Carper