The other solutions seem higher level, but it's worth noting that
destructuring -- (let [[x & xs] lst] ...) -- uses next and is therefore
not fully lazy in that you will peek ahead by one into the lazy
sequence, so to speak. You have to use explicit first / rest to get
that:
;; with destructuring
(defn flatten [lst]
(lazy-seq
(if (empty? lst) lst
(let [[x & xs] lst]
(if (list? x)
(concat (flatten x) (flatten xs))
(cons x (flatten xs)))))))
;; explicit first / rest
(defn flatten-2 [lst]
(lazy-seq
(if-let [x (first lst)]
(let [xs (rest lst)]
(if (seq? x)
(concat (flatten-2 x) (flatten-2 xs))
(cons x (flatten-2 xs)))))))
;; returns a fresh, unevaluated, lazy seq each time
(defn lazy-integers []
(map #(do (print (str "[" % "] ")) %)
(iterate inc 0)))
Then:
user> (take 5 (flatten (lazy-integers)))
([0] [1] [2] 0 [3] 1 [4] 2 [5] 3 4)
user> (take 5 (flatten-2 (lazy-integers)))
([0] [1] 0 [2] 1 [3] 2 [4] 3 4)
So, flatten-2 never looks at the 6th element, 5, when it returns the
first 5. It's fully lazy in that it only evaluates the elements needed
for the result.
Of course, this is not a problem unless the lazy seq contains
computationally intensive or has side effects that need to be accounted
for in other ways.
-Sudish