Browse Learn Clojure Foundations as a Java Developer

Creating Lazy Sequences in Clojure

Create lazy sequences with lazy-seq, repeat, range, iterate, and sequence transformations while keeping realization and memory retention visible.

Lazy sequences let Clojure describe values before all of them exist. You can create them directly with lazy-seq or use built-in producers such as repeat, range, and iterate; the engineering question is always where the sequence will be realized.

Understanding Lazy Sequences

In Clojure, a lazy sequence is a sequence whose elements are computed on demand. This means elements are not generated until they are needed, which can avoid unnecessary work when a consumer only needs part of a large or infinite source.

Key Benefits of Lazy Sequences

  • Avoided Work: Only demanded elements are computed.
  • Composability: Lazy sequences can be easily composed and transformed using Clojure’s rich set of sequence operations.
  • Infinite Data Structures: Lazy sequences enable the creation of infinite data structures, which can be processed incrementally.

Creating Lazy Sequences with lazy-seq

The lazy-seq function is a fundamental building block for creating lazy sequences in Clojure. It allows you to define a sequence where each element is computed only when needed.

1(defn lazy-fib
2  "Generates an infinite lazy sequence of Fibonacci numbers."
3  ([] (lazy-fib 0 1))
4  ([a b]
5   (lazy-seq
6     (cons a (lazy-fib b (+ a b))))))

In this example, lazy-fib generates an infinite sequence of Fibonacci numbers. The lazy-seq function ensures that each Fibonacci number is computed only when it is accessed.

Java Comparison

In Java, a similar unbounded source usually uses an Iterator, a custom class, or Stream.iterate. The Clojure version is shorter because lazy sequence producers compose directly with the normal sequence API.

Using repeat for Lazy Sequences

The repeat function generates an infinite lazy sequence of a given value. This can be useful for creating constant sequences or initializing data structures.

1(def infinite-ones (repeat 1))
2
3(take 5 infinite-ones) ; => (1 1 1 1 1)

Here, infinite-ones is an infinite sequence of the number 1. The take function is used to retrieve the first five elements.

Java Comparison

In Java, you might use a loop or a stream to achieve similar functionality, but the surrounding API shape is different from Clojure’s ordinary sequence operations.

Generating Sequences with range

The range function creates a lazy sequence of numbers. It can generate finite or infinite sequences depending on the arguments provided.

1(def numbers (range 10)) ; Finite sequence from 0 to 9
2
3(def infinite-numbers (range)) ; Infinite sequence starting from 0
4
5(take 5 infinite-numbers) ; => (0 1 2 3 4)

The range function is versatile and can be used to generate sequences with specific start, end, and step values.

Java Comparison

Java’s Stream API introduced in Java 8 provides similar functionality with methods like IntStream.range(), but Clojure’s range is more concise and integrates seamlessly with other sequence operations.

Creating Sequences with iterate

The iterate function generates a lazy sequence by repeatedly applying a function to an initial value.

1(def powers-of-two (iterate #(* 2 %) 1))
2
3(take 5 powers-of-two) ; => (1 2 4 8 16)

In this example, iterate is used to create a sequence of powers of two. The function #(* 2 %) is applied to each element to generate the next one.

Java Comparison

Java’s Stream.iterate() provides similar functionality, but Clojure’s iterate is more idiomatic for functional programming and integrates better with Clojure’s sequence operations.

Combining Lazy Sequences

Lazy sequences in Clojure can be combined and transformed using various sequence operations. This composability is one of the key strengths of Clojure’s approach to lazy evaluation.

1(def even-fibs
2  (filter even? (lazy-fib)))
3
4(take 5 even-fibs) ; => (0 2 8 34 144)

In this example, we use filter to create a new lazy sequence of even Fibonacci numbers. The filter function itself returns a lazy sequence, ensuring that only the necessary elements are computed.

Visualizing Lazy Sequences

To better understand how lazy sequences work, let’s visualize the flow of data through a series of transformations.

    graph TD;
	    A[Lazy Sequence] --> B[Transformation 1];
	    B --> C[Transformation 2];
	    C --> D[Final Result];

This diagram represents the flow of data through a series of transformations, each of which is applied lazily.

Try It Yourself

Experiment with the code examples provided by modifying the functions or parameters. For instance, try creating a lazy sequence of prime numbers or a sequence that generates random numbers.

Exercises

  1. Create a lazy sequence of squares of natural numbers.
  2. Implement a lazy sequence that generates the sequence of factorials.
  3. Use lazy-seq to create a sequence of the first 100 prime numbers.

Key Takeaways

  • Lazy sequences in Clojure allow for efficient computation and memory usage by generating elements on demand.
  • Functions like lazy-seq, repeat, range, and iterate provide powerful tools for creating and manipulating lazy sequences.
  • Clojure’s approach to lazy evaluation offers significant advantages over Java’s traditional methods, particularly in terms of composability and simplicity.

Further Reading

Now that we’ve explored how to create and work with lazy sequences in Clojure, let’s apply these concepts to efficiently process large datasets and streams in your applications.

Quiz: Creating Lazy Sequences

### What is a key benefit of lazy sequences in Clojure? - [x] They compute elements only when needed, improving efficiency. - [ ] They store all elements in memory, ensuring fast access. - [ ] They require explicit iteration to access elements. - [ ] They are always finite. > **Explanation:** Lazy sequences compute elements on demand, which can avoid unnecessary work and realization. ### Which function is used to create a lazy sequence of repeated values? - [ ] lazy-seq - [x] repeat - [ ] range - [ ] iterate > **Explanation:** The `repeat` function generates an infinite lazy sequence of a given value, making it ideal for creating constant sequences. ### How does the `range` function in Clojure differ from Java's `IntStream.range()`? - [x] Clojure's `range` is more concise and integrates seamlessly with other sequence operations. - [ ] Java's `IntStream.range()` is more concise and integrates seamlessly with other sequence operations. - [ ] Both functions are identical in functionality and usage. - [ ] Clojure's `range` requires more boilerplate code. > **Explanation:** Clojure's `range` is more concise and idiomatic for functional programming, allowing for seamless integration with other sequence operations. ### What does the `iterate` function do in Clojure? - [x] It generates a lazy sequence by repeatedly applying a function to an initial value. - [ ] It creates a finite sequence of numbers. - [ ] It generates a sequence of random numbers. - [ ] It creates a sequence of repeated values. > **Explanation:** The `iterate` function generates a lazy sequence by applying a function to an initial value, producing each subsequent element. ### Which of the following is a correct use of `lazy-seq`? - [x] `(lazy-seq (cons 1 (lazy-seq (cons 2 nil))))` - [ ] `(lazy-seq 1 2 3)` - [ ] `(lazy-seq [1 2 3])` - [ ] `(lazy-seq (1 2 3))` > **Explanation:** The correct use of `lazy-seq` involves wrapping a sequence construction, such as `cons`, to ensure elements are computed lazily. ### What is the result of `(take 5 (repeat 3))`? - [x] `(3 3 3 3 3)` - [ ] `(3)` - [ ] `(3 3 3)` - [ ] `(3 3 3 3 3 3)` > **Explanation:** The `repeat` function generates an infinite sequence of the value `3`, and `take 5` retrieves the first five elements. ### How can lazy sequences improve performance? - [x] By computing only the necessary elements, reducing memory usage and computation time. - [ ] By storing all elements in memory for fast access. - [ ] By requiring explicit iteration to access elements. - [ ] By ensuring all elements are computed upfront. > **Explanation:** Lazy sequences improve performance by computing only the necessary elements, which reduces memory usage and computation time. ### Which function would you use to create an infinite sequence of natural numbers? - [ ] repeat - [ ] iterate - [x] range - [ ] lazy-seq > **Explanation:** The `range` function can be used to create an infinite sequence of natural numbers by calling it without arguments. ### What is the purpose of the `filter` function in the context of lazy sequences? - [x] To create a new lazy sequence containing only elements that satisfy a predicate. - [ ] To generate a sequence of repeated values. - [ ] To create a finite sequence of numbers. - [ ] To apply a function to each element of a sequence. > **Explanation:** The `filter` function creates a new lazy sequence containing only elements that satisfy a given predicate, allowing for efficient data processing. ### True or False: Lazy sequences in Clojure are always finite. - [ ] True - [x] False > **Explanation:** Lazy sequences in Clojure can be infinite, as they compute elements on demand and do not require all elements to be generated upfront.
Revised on Saturday, May 23, 2026