Clojure: Как вернуться к исключению?
Я пытаюсь выполнить func несколько раз, прежде чем отказаться от исключений.
Но это недействительно в Clojure для возврата из блока catch.
Как это можно достичь?
(loop [tries 10]
(try
(might-throw-exception)
(catch Exception e
(when (pos? tries) (recur (dec tries))))))
java.lang.UnsupportedOperationException: Cannot recur from catch/finally
Лучшее, что я смог найти, это следующее неуклюжие решения (обертывание в func и вызов его)
(defn do-it []
(try
(might-throw-exception)
(catch Exception e nil)))
(loop [times 10]
(when (and (nil? (do-it)) (pos? times))
(recur (dec times))))
Ответы
Ответ 1
Макросы вызывают...
Как насчет этого:
(defn try-times*
"Executes thunk. If an exception is thrown, will retry. At most n retries
are done. If still some exception is thrown it is bubbled upwards in
the call chain."
[n thunk]
(loop [n n]
(if-let [result (try
[(thunk)]
(catch Exception e
(when (zero? n)
(throw e))))]
(result 0)
(recur (dec n)))))
(defmacro try-times
"Executes body. If an exception is thrown, will retry. At most n retries
are done. If still some exception is thrown it is bubbled upwards in
the call chain."
[n & body]
`(try-times* ~n (fn [] [email protected])))
Ответ 2
Идея kotarak - это путь, но этот вопрос щекотал мою фантазию, поэтому я хотел бы предоставить рифф на ту же тему, которую я предпочитаю, потому что она не использует loop/recur:
(defn try-times* [thunk times]
(let [res (first (drop-while #{::fail}
(repeatedly times
#(try (thunk)
(catch Throwable _ ::fail)))))]
(when-not (= ::fail res)
res)))
И оставить макрос try-times как есть.
Если вы хотите разрешить thunk возвращать nil, вы можете отпустить пару let/when и позволить:: fail представлять "сбой функции n раз", в то время как nil означает "возвращаемая функция nil". Такое поведение было бы более гибким, но менее удобным (вызывающий должен проверить, чтобы:: не смог увидеть, работает ли он, а не только нуль), поэтому, возможно, его лучше всего реализовать как необязательный второй параметр:
(defn try-times* [thunk n & fail-value]
(first (drop-while #{fail-value} ...)))
Ответ 3
Мое предложение:
(defmacro try-times
"Retries expr for times times,
then throws exception or returns evaluated value of expr"
[times & expr]
`(loop [err# (dec ~times)]
(let [[result# no-retry#] (try [(do [email protected]) true]
(catch Exception e#
(when (zero? err#)
(throw e#))
[nil false]))]
(if no-retry#
result#
(recur (dec err#))))))
Будет напечатан "нет ошибок здесь" один раз:
(try-times 3 (println "no errors here") 42)
Будет напечатано три раза три раза, затем разделите Divide на ноль:
(try-times 3 (println "trying") (/ 1 0))
Ответ 4
Макрос A try-times
является элегантным, но для одноразового просто вытащите when
из блока try
:
(loop [tries 10]
(when (try
(might-throw-exception)
false ; so 'when' is false, whatever 'might-throw-exception' returned
(catch Exception e
(pos? tries)))
(recur (dec tries))))
Ответ 5
Еще одно решение без макроса
(defn retry [& {:keys [fun waits ex-handler]
:or {ex-handler #(log/error (.getMessage %))}}]
(fn [ctx]
(loop [[time & rem] waits]
(let [{:keys [res ex]} (try
{:res (fun ctx)}
(catch Exception e
(when ex-handler
(ex-handler e))
{:ex e}))]
(if-not ex
res
(do
(Thread/sleep time)
(if (seq rem)
(recur rem)
(throw ex))))))))