Clojure中使用recur参数数量错误困惑及round-robin函数实现问询
Let’s break down your questions one by one:
Why does using recur cause an argument count error?
First, let’s recap how your original recursive implementation works: it filters out empty collections, then uses lazy-cat to concatenate the first elements of all non-empty collections with the result of recursively processing the "rest" of each collection.
The problem with swapping the recursive call for recur boils down to two key points:
recurrequires tail position:recuronly works when the recursive call is the very last operation the function performs. In your code, the recursive call is the second argument tolazy-cat—the function has to first process(map first colls)before evaluating the recursive part. This means the recursive call isn’t in tail position, so Clojure won’t allowrecurhere.- Accidental multiple arguments: If you tried to pass extra parameters to
recur(e.g., mistakenly includingcollsalongside(map next colls)), you’d get an argument count error immediately, since your function only accepts one parameter (all-colls).
For example, this invalid attempt would throw an error because recur isn’t in tail position:
(defn round-robin [all-colls] (let [colls (filter seq all-colls)] (if (seq colls) (lazy-cat (map first colls) (recur (map next colls)))))) ;; Invalid: recur not in tail position
Better Implementations for Round-Robin
Your original code is already lazy and correct, but we can refine it for readability or idiomatic Clojure style:
1. Concise High-Order Function Approach
We can use iterate to generate each round of collection states, then mapcat to flatten the first elements of each round:
(defn round-robin [all-colls] (mapcat first (take-while seq (iterate #(filter seq (map next %)) (filter seq all-colls)))))
Here’s how it works:
(filter seq all-colls)starts with the initial non-empty collections.iteraterepeatedly generates the next state by takingnextof each collection and filtering out empties.take-while seqstops when all collections are empty.mapcat firstconcatenates the first elements of every valid round into a single sequence.
Testing this with your example:
(round-robin [[:a1 :a2 :a3] [:b1] [:c1 :c2]]) ;; => (:a1 :b1 :c1 :a2 :c2 :a3)
2. Tail-Recursive Lazy Implementation
If you prefer explicit tail recursion (to avoid potential stack overflow for very large sequences), wrap loop/recur in lazy-seq:
(defn round-robin [all-colls] (lazy-seq (loop [colls (filter seq all-colls)] (when (seq colls) (concat (map first colls) (lazy-seq (recur (map next colls))))))))
This maintains the laziness of the original implementation while using recur correctly in a tail position inside the lazy-seq wrapper.
内容的提问来源于stack exchange,提问作者Phil Cooper

