Search code examples
loopsclojure

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.


Solution

  • 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.