Search code examples
clojuretail-recursion

recur in the rightful tail poistion


recur should be called in the tail position and I assume that it effectively acts as non-recursive quasi-loop.

Is expr-1 or 2 regarded in the rightful tail position but none of expr-3 till 8 in the following mimic block structures? Otherwise, how to reason and identify it before resort to trail and error?

(defn foo [x]
 (if cond-expr-1
  (recur expr-1)
  (recur expr-2)))

(defn bar [x]
 (if cond-expr-2
  (fn-1 (recur expr-3))
  (fn-2 (recur expr-4))))

(defn baz [x]
 (if cond-expr-3
  (if cond-expr-4  
    (recur expr-5)
    (recur expr-6))
  (if cond-expr-5  
    (recur expr-7)
    (recur expr-8))))

Solution

  • In the case of expr-3 and expr-4, it is the argument of a function, and that is why it is not in tail position.

    Calling recur is basically like a goto or return statement, both of which cannot be used inside a function argument list.

    Since the if expression is not a function (it is a "special form"), there is no problem as with a function arg list.


    Here is a comparison between loop/recur and a more imperative approach using while:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test))
    
    (defn fib-recur
      [arg]
      (assert (and (int? arg) (pos? arg)))
      ; initialize state vars
      (loop [N      arg
             result 1]
        (if (zero? N)
          result
          ; compute next state vars
          (let [N-next      (dec N)
                result-next (* result N)]
            (recur N-next result-next))))) ; jump to top of loop with next state vars
    
    (defn fib-while
      [arg]
      (assert (and (int? arg) (pos? arg)))
      ; initialize state vars
      (let [state (atom {:N      arg
                         :result 1})]
        (while (pos? (:N @state)) ; must use newest state value for N in test
          ; compute next state vars
          (let [N          (:N @state)
                result     (:result @state)
                state-next {:N      (dec N)
                            :result (* result N)}]
            (reset! state state-next))) ; save new state & jump to top of loop
        (:result @state)))
    
    (dotest
      (is= 120 (fib-recur 5))
      (is= 120 (fib-while 5)))