【问题标题】:Clojure: How to to recur upon exception?Clojure:如何在异常时重现?
【发布时间】:2009-12-10 09:44:23
【问题描述】:

我试图在放弃异常之前多次执行一个函数。 但是在 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))))

【问题讨论】:

    标签: clojure


    【解决方案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 [] ~@body)))

    【讨论】:

    • 这是一个很好的解决方案。我会将它添加到 clojure.contrib 或其他内容中。
    • 这实际上与发布者建议的解决方案相同。但是宏在一般情况下更容易做到。宏是任何 lisp 变体的杀手级功能。
    • 这不是完全相同的解决方案。发帖人的建议没有捕捉到块的返回值,如果捕捉到了,那么块将无法返回 nil。异常也被吞没了。但你是对的:这基本上是相同的想法。宏只是隐藏样板。
    • 我能问你为什么将 thunk 的结果放在向量中吗?我不知道为什么你不能把它当作一个“裸”值?
    • @ChristopheDeTroyer 否则如果 (thunk) 返回 nil,它将被视为 if-let 的错误结果
    【解决方案2】:

    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 对,让 ::fail 表示“函数失败 n 次”,而 nil 表示“函数返回 nil”。这种行为会更灵活但不太方便(调用者必须检查 ::fail 以查看它是否有效,而不仅仅是 nil),因此最好将其实现为可选的第二个参数:

    (defn try-times* [thunk n & fail-value]
      (first (drop-while #{fail-value} ...)))
    

    【讨论】:

    • 可能,如果您遇到错误之一(Throwable 的后代),您不想重试...
    【解决方案3】:

    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))))
    

    【讨论】:

      【解决方案4】:

      我的建议:

      (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#))))))
      

      将打印一次“这里没有错误”:

      (try-times 3 (println "no errors here") 42)
      

      将打印“trying” 3 次,然后抛出除以零:

      (try-times 3 (println "trying") (/ 1 0))
      

      【讨论】:

        【解决方案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))))))))
        

        【讨论】:

          【解决方案6】:

          这允许捕获多个多于一个的异常,并提供有关重试原因的一些反馈。

          (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#})))))
          

          【讨论】:

            【解决方案7】:

            如果在循环中添加 result 参数,则可以将 (try) 块嵌套在 (recur) 内。我是这样解决的:

            (loop [result nil tries 10]
              (cond (some? result) result
                    (neg? tries) nil
                    :else (recur (try (might-throw-exception)
                                      (catch Exception e nil))
                                 (dec tries))))
            

            【讨论】:

              【解决方案8】:

              这是另一种方法:

              (loop [tries 10]
                (let [res (try
                            (might-throw-exception)
                            (catch Exception e
                              (if (pos? tries)
                                ::retry
                                (throw e))))]
                  (if (#{::retry} res)
                    (recur (dec tries))
                    res)))
              

              但是我也可以推荐一个很酷的小技巧,而不是进行多次重试,而是提供一系列睡眠时间:

              (loop [tries [10 10 100 1000]]
                (let [res (try
                            (might-throw-exception)
                            (catch Exception e
                              (if tries
                                ::retry
                                (throw e))))]
                  (if (#{::retry} res)
                    (do
                      (Thread/sleep (first tries))
                      (recur (next tries)))
                    res)))
              

              如果你想让它不那么冗长,最后把它全部放在一个宏中:

              (defmacro with-retries
                [retries & body]
                `(loop [retries# ~retries]
                   (let [res# (try ~@body
                                   (catch Exception e#
                                     (if retries#
                                       'retry#
                                       (throw e#))))]
                     (if (= 'retry# res#)
                       (do (Thread/sleep (first retries#))
                           (recur (next retries#)))
                       res#))))
              
              (with-retries [10 10 100 1000]
                (might-throw-exception))
              

              【讨论】:

                猜你喜欢
                • 2011-07-24
                • 2013-02-03
                • 2016-09-09
                • 2022-12-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多