Clojure:如何在异常情况下重现?

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

在异常情况下放弃之前,我尝试执行了几次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 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#})))))