Search code examples
loopsrecursionclojurelet

Clojure - recur applies to loop or let statement?


I have a question in Clojure about recur. If I have a let-statement inside the loop, can the recur call be applying to the let statement instead of the values of the loop? For example, in this scenario:

(defn someFunction [listA listB]
  ("do something here...." 
      [new-listA new-listB]))

(defn anotherFunction [listA listB]
  ("do something here...." 
      [new-listA new-listB]))

(defn myFunction [firstList secondList]
  (loop [list1 (someMutation firstList)
         list2 (someMutation secondList)]
    (if (= "true" (someCondition))
      (let [[newlist1 newlist2]
           (someFunction list1 list2)] 
        (recur newlist1 newlist2))
      (anotherFunction list1 list2) )))

is (recur newlist1 newlist2) applying to the loop or to the let? And is there a way of skipping this let statement and calling recur directly with the two values returned by "someFunction", assuming that I can't change the fact that "someFunction" returns a vector with two arguments?


Solution

  • recur always recurs to the closest loop or function, whichever is closer to the recur form. It does this by compiling to essentially a loop/jump statement that changes the values of the loop variables and then jumps back to the loop/fn point. This way it only uses one stack frame and can run full speed. This design has some intentional trade-offs:

    • you cannot interleave nested loops
    • recur can only be the last thing in an expression.
    • there must be the same number of args to recur as loop/the-function

    This last one requires that you keep the let expression or something equivalent such as a destructuring form. Loops do allow destructuring of the arguments just like functions:

     (loop [[a b] [1 2]]
        (println a b)
        (if (< a 10)
          (recur [(inc a) (inc b)])))
    1 2
    2 3
    3 4
    4 5
    5 6
    6 7
    7 8
    8 9
    9 10
    10 11
    

    So you could write your loop expression to take the result of someFunction directly

    (loop [[list1 list2] [(someMutation firstList) (someMutation secondList)]]
      ...
      (recur (someFunction ...))