Clojure's recur function can only occur in a tail position. However, this does not work directly when using a loop/recur to repeat a possibly failing operation.
In this case, wrap the operation in a thunk, and return the value in a tuple of [result exception], where either the result or the exception is set (that's XOR). Then a simple 'if' can be used to determine if a result is returned or another attempt should be made.
A function that takes a thunk and a number of tries looks like this:
(defn try-loop [thunk tries] (loop [n tries ex nil] (if (zero? n) (and ex (throw ex)) (let [[result exception] (try [(thunk) nil] (catch Throwable t [nil t]))] (if-not exception result (recur (dec n) (or ex exception)))))))
Comments on this:
- (if (zero? n) - tests if the retries count has finished.
- (and ex (throw ex)) - only throw ex if not nil.
- (recur (dec n) (or ex exception)) - the "(or ex exception)" is used to keep the first exception, instead of the last.