Clojure:如何在异常情况下重现?
在异常情况下放弃之前,我尝试执行了几次func。 但在Clojure中,从catch block中重复出现是无效的。 如何做到这一点Clojure:如何在异常情况下重现?,clojure,Clojure,在异常情况下放弃之前,我尝试执行了几次func。 但在Clojure中,从catch block中重复出现是无效的。 如何做到这一点 (loop [tries 10] (try (might-throw-exception) (catch Exception e (when (pos? tries) (recur (dec tries)))))) java.lang.UnsupportedOperationException: Cannot recur from
(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 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 [] ~@body)))
kotarak的想法是可行的,但这个问题让我很高兴,所以我想提供一个与我喜欢的主题相同的即兴片段,因为它不使用循环/重现:
(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对,let::fail表示“函数失败n次”,而nil表示“函数返回nil”。此行为将更灵活,但不太方便(调用方必须检查::fail是否有效,而不仅仅是nil),因此最好将其实现为可选的第二个参数:
(defn try-times* [thunk n & fail-value]
(first (drop-while #{fail-value} ...)))
我的建议:
(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 ~@expr) true]
(catch Exception e#
(when (zero? err#)
(throw e#))
[nil false]))]
(if no-retry#
result#
(recur (dec err#))))))
将打印一次“此处无错误”:
将打印“尝试”3次,然后抛出除以零:
(try-times 3 (println "trying") (/ 1 0))
还有一个解决方案,没有宏
(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))))))))
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))))
这允许捕获多个异常,然后捕获一个异常,并提供有关重试原因的一些反馈
(defmacro try-n-times
"Try running the body `n` times, catching listed exceptions."
{:style/indent [2 :form :form [1]]}
[n exceptions & body]
`(loop [n# ~n
causes# []]
(if (> n# 0)
(let [result#
(try
~@body
~@(map (partial apply list 'catch) exceptions (repeat `(e# e#))))]
(if (some #(instance? % result#) ~exceptions)
(recur (dec n#) (conj causes# result#))
result#))
(throw (ex-info "Maximum retries exceeded!"
{:retries ~n
:causes causes#})))))
如果将
结果
参数添加到循环中,则可以将(try)
块嵌套在(recur)
中。我是这样解决的:
(循环[结果nil尝试10]
(第二个结果)
(阴性)无
:else(重复(try)(可能引发异常)
(无捕获例外)
(十二月)
这是一个很好的解决方案。我会将它添加到clojure.contrib或其他什么东西中。它实际上与海报建议的解决方案相同。但在一般情况下,宏更容易实现。宏是任何lisp变体的杀手级功能。它不是完全相同的解决方案。海报的建议没有捕捉到块的返回值,如果捕捉到,块将无法返回零。此外,例外情况也被忽略了。但你是对的:这基本上是相同的想法。宏只是隐藏了样板文件。我能问你为什么要把thunk的结果放在向量中吗?我不知道你为什么不能把它作为一个“裸”值?@ChristophedDetroyer,否则如果(thunk)返回nil,它将被视为if-let的一个错误结果。也许,如果你得到一个错误(Throwable的后代),你就不想重试了。。。
(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))))
(defmacro try-n-times
"Try running the body `n` times, catching listed exceptions."
{:style/indent [2 :form :form [1]]}
[n exceptions & body]
`(loop [n# ~n
causes# []]
(if (> n# 0)
(let [result#
(try
~@body
~@(map (partial apply list 'catch) exceptions (repeat `(e# e#))))]
(if (some #(instance? % result#) ~exceptions)
(recur (dec n#) (conj causes# result#))
result#))
(throw (ex-info "Maximum retries exceeded!"
{:retries ~n
:causes causes#})))))