I'm trying to understand clojure's lazy-seq
operator, and the concept of lazy evaluation in general. I know the basic idea behind the concept: Evaluation of an expression is delayed until the value is needed.
In general, this is achievable in two ways:
- at compile time using macros or special forms;
- at runtime using lambda functions
With lazy evaluation techniques, it is possible to construct infinite data structures that are evaluated as consumed. These infinite sequences utilizes lambdas, closures and recursion. In clojure, these infinite data structures are generated using lazy-seq
and cons
forms.
I want to understand how lazy-seq
does it's magic. I know it is actually a macro. Consider the following example.
(defn rep [n]
(lazy-seq (cons n (rep n))))
Here, the rep
function returns a lazily-evaluated sequence of type LazySeq
, which now can be transformed and consumed (thus evaluated) using the sequence API. This API provides functions take
, map
, filter
and reduce
.
In the expanded form, we can see how lambda is utilized to store the recipe for the cell without evaluating it immediately.
(defn rep [n]
(new clojure.lang.LazySeq (fn* [] (cons n (rep n)))))
- But how does the sequence API actually work with
LazySeq
? - What actually happens in the following expression?
(reduce + (take 3 (map inc (rep 5))))
- How is the intermediate operation
map
applied to the sequence, - how does
take
limit the sequence and - how does terminal operation
reduce
evaluate the sequence?
Also, how do these functions work with either a Vector
or a LazySeq
?
Also, is it possible to generate nested infinite data structures?: list containing lists, containing lists, containing lists... going infinitely wide and deep, evaluated as consumed with the sequence API?
And last question, is there any practical difference between this
(defn rep [n]
(lazy-seq (cons n (rep n))))
and this?
(defn rep [n]
(cons n (lazy-seq (rep n))))