Practice loop/recur with counters, accumulators, collection scans, and state transitions so Java loop patterns translate into clear Clojure code.
Java loop patterns usually contain a condition, one or more changing values, and a final result. loop/recur makes those pieces explicit in Clojure: the loop binding vector names the current state, and the recur call supplies the next state.
loop/recurBefore we dive into examples, let’s briefly understand how loop/recur works. The loop construct establishes a recursion point, and recur jumps back to that point from a valid tail position. That jump reuses the current frame instead of consuming stack space with each iteration.
Here’s a simple example to illustrate the syntax:
1(loop [i 0]
2 (when (< i 10)
3 (println i)
4 (recur (inc i))))
In this example, loop initializes a local binding i with the value 0. The when condition checks if i is less than 10, and if true, it prints i and calls recur with the incremented value of i. This process repeats until i reaches 10.
loop/recurLet’s explore some iterative algorithms using loop/recur. We’ll start with a simple example and gradually move to more complex scenarios.
Calculating the factorial of a number is a classic example of recursion. However, it can also be implemented iteratively using loop/recur.
1(defn factorial [n]
2 (loop [acc 1
3 i n]
4 (if (zero? i)
5 acc
6 (recur (* acc i) (dec i)))))
7
8(println (factorial 5)) ; Output: 120
Explanation:
factorial that takes a single argument n.loop initializes two bindings: acc (accumulator) with 1 and i with n.i is zero, we return acc, which holds the factorial result.acc by i and decrement i, then call recur.The Fibonacci sequence is another classic example. Let’s implement it using loop/recur.
1(defn fibonacci [n]
2 (loop [a 0
3 b 1
4 i n]
5 (if (zero? i)
6 a
7 (recur b (+ a b) (dec i)))))
8
9(println (fibonacci 10)) ; Output: 55
Explanation:
fibonacci that takes n as an argument.loop initializes a with 0, b with 1, and i with n.i is zero, we return a, which holds the nth Fibonacci number.a to b, b to a + b, and decrement i, then call recur.loop/recurClojure’s loop/recur can also be used to process collections. Let’s see how we can sum the elements of a vector.
1(defn sum-vector [v]
2 (loop [acc 0
3 coll v]
4 (if (empty? coll)
5 acc
6 (recur (+ acc (first coll)) (rest coll)))))
7
8(println (sum-vector [1 2 3 4 5])) ; Output: 15
Explanation:
sum-vector that takes a vector v.loop initializes acc with 0 and coll with v.coll is empty, we return acc, which holds the sum.coll to acc and call recur with the rest of coll.loop/recur can be used to simulate state changes over time, such as in a simple game or simulation.
Let’s simulate a counter that increments every second until it reaches a specified limit.
1(defn simulate-counter [limit]
2 (loop [count 0]
3 (when (< count limit)
4 (println "Count:" count)
5 (Thread/sleep 1000) ; Sleep for 1 second
6 (recur (inc count)))))
7
8(simulate-counter 5)
Explanation:
simulate-counter that takes a limit.loop initializes count with 0.count is less than limit, we print the count, sleep for 1 second, and call recur with the incremented count.In Java, similar iterative logic would typically be implemented using for or while loops. Here’s how the factorial example might look in Java:
1public class Factorial {
2 public static int factorial(int n) {
3 int acc = 1;
4 for (int i = n; i > 0; i--) {
5 acc *= i;
6 }
7 return acc;
8 }
9
10 public static void main(String[] args) {
11 System.out.println(factorial(5)); // Output: 120
12 }
13}
Comparison:
for loop to iterate from n down to 1, multiplying acc by i in each iteration.loop/recur achieves the same result with a more functional approach, emphasizing immutability and recursion.Now that we’ve explored some examples, try modifying the code to deepen your understanding:
factorial function to handle negative numbers gracefully.fibonacci function to return a sequence of Fibonacci numbers up to n.loop/recur.simulate-counter function to decrement the counter instead of incrementing it.To help visualize the flow of loop/recur, consider the following diagram illustrating the flow of a simple loop:
graph TD;
A[Start] --> B[Initialize Variables];
B --> C{Condition Met?};
C -->|Yes| D[Perform Action];
D --> E[Update Variables];
E --> C;
C -->|No| F[End];
Diagram Description: This flowchart represents the iterative process of a loop/recur construct in Clojure. It starts by initializing variables, checks a condition, performs an action if the condition is met, updates variables, and repeats until the condition is no longer met.
loop/recur that checks if a number is prime.loop/recur to reverse a list without using built-in functions.loop/recur to update the character’s position.loop/recur provides an explicit way to perform custom iteration in Clojure.recur reuses the current frame when it is in tail position.loop/recur passes the next values as bindings.loop/recur is versatile and efficient.Use loop/recur when the loop has custom state transitions. If the example can be expressed more clearly with map, filter, or reduce, prefer the higher-level function.
For further reading, explore the Official Clojure Documentation and ClojureDocs for more examples and detailed explanations.