Oin
Oin

Reputation: 7529

How do I flatten params passed to recur?

I'm trying to learn about loop/recur. I wanted to pass the return vector from a function back to the loop and I've tried something like this:

(defn foo [x y] 
  [(dec x) y])

(loop [x 3 y 4]
  (if (> x 0)
    (do
      (prn x y)
      (recur (foo x y)))))

That gives:

1. Caused by java.lang.IllegalArgumentException
   Mismatched argument count to recur, expected: 2 args, got: 1

Now I can change the loop parameters to this other form which works:

(defn foo [x y] 
  [(dec x) y])

(loop [[x y] [3 4]]
  (if (> x 0)
    (do
      (prn x y)
      (recur (foo x y)))))

I'd like to know if there's any way I could change the first code to leave the (loop [x 3 y 4] ...) but change the arguments passed to recur somehow. I guess I need something like the apply function, but I couldn't get that to work with recur, because recur is not a function.

Upvotes: 1

Views: 110

Answers (2)

leetwinski
leetwinski

Reputation: 17859

you could also make up a simple macro for that purpose.. maybe like this (which is basically a macro rewrite of your vectorized params variant):

(defmacro loop1 [bindings & body]
  (let [left-side (vec (take-nth 2 bindings))
        right-side (vec (take-nth 2 (rest bindings)))]
    `(loop [~left-side ~right-side]
       ~@body)))

(loop1 [x 3 y 4]
       (if (> x 0)
         (do
           (prn x y)
           (recur (foo x y)))))

;;=> 3 4
;;   2 4
;;   1 4
nil

so, if you have repetitive usage pattern alike that, this macro could be a nice utility.

Upvotes: 2

cfrick
cfrick

Reputation: 37008

There is no easy way around it. recur is a special form and Clojures way around the JVM not having easy access to TCO.

Therefor you can not use apply here (which would be a solution to call a function with arguments from a list).

So you have to hold on to the result of foo and then recur with those. Note beside: if with just one branch is just when.

(defn foo [x y] 
  [(dec x) y])

(loop [x 3 y 4]
  (when (> x 0)
    (prn x y)
    (let [[x' y'] (foo x y)] 
      (recur x' y'))))

If your domain here really is [x y] (e.g. coordinates) my suggestion would be to build your functions around that and don't jump between sometimes using a vector and sometimes passing x/y.

Upvotes: 6

Related Questions