Currently, it's not possible to recur
across a try
block:
(let [RETRIABLE-FUNCTION (fn [] (println "Function called") (throw (ex-info "Function failed" {})))]
(loop [retries 3]
(try
(RETRIABLE-FUNCTION)
(catch Exception ex
(when (< 1 retries)
(recur (dec retries)))))))
Syntax error (UnsupportedOperationException) compiling recur at (src/repl.clj:7:13). Can only recur from tail position
So instead on has to rewrite the code as something like this:
(let [RETRIABLE-FUNCTION (fn [] (println "Function called") (throw (ex-info "Function failed" {})))]
(loop [retries 3]
(let [result (try
(RETRIABLE-FUNCTION)
(catch Exception ex
(when (< 1 retries)
::retry)))]
(if (= ::retry result)
(recur (dec retries))
result))))
— which is pretty unwieldy, especially in the cases of nested retries or more complex state machines.
Alternatively, one could use one of many retry macro libraries — which is a pretty heavy price for such simple use case, I think. And usually much less flexible than what one could do in-place.
"Ideologically", it seems to me that there should be problem there, as the last expression in catch look to me like a tail position.
Is there a technical limitation of the JVM that makes the recur-across-try impossible (or maybe has very low performance, so it's a good idea to discourage that)? Or is it just a low-priority quality-of-life feature, so it never got to an implementation?