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))))
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)))