Объяснение заключается в макроразложении with-redefs
:
(macroexpand-1
'(with-redefs []
(if (zero? test)
"done"
(recur (dec test)))))
возвращает:
(with-redefs-fn {}
(fn []
(if (zero? test)
"done"
(recur (dec test)))))
, где вы можете видеть, что, поскольку был введен новый fn
,recur
будет ссылаться на этот fn
, а не на дальний loop
(что объясняет исключение арности).
Существует множество других макросов, которые "несовместимы" с loop
таким образом, поскольку recur
должен находиться в хвостовой позиции по отношению к loop
, и если recur
происходит внутри вызова макроса, макрос может манипулировать кодом так, что recur
больше не в хвостовом положении.
В частности, with-redefs
(и ряде других ситуаций), обходной путь может быть:
(loop [test 3]
(let [[recur? val]
(with-redefs []
(if (zero? test)
[false "done"]
[true (dec test)]))]
(if recur?
(recur val)
val)))