2009-12-10 79 views
37

我想在放弃异常之前执行一次func几次。 但是它在Clojure中无法从catch block重现。 这是如何实现的?Clojure:如何在异常时重现?

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

回答

41

宏呼吁...

如何:

(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])))
+0

这是一个好方法。我会将它添加到clojure.contrib或其他东西。 – GabiMe 2009-12-10 10:26:20

+0

它实际上和海报建议的解决方案一样。但宏在一般情况下更容易实现。宏是任何lisp变体的杀手锏。 – 2009-12-10 18:17:26

+0

这不完全相同的解决方案。海报的建议不会捕获块的返回值,并且如果它发生该块将无法返回nil。吞下例外。但你是对的:它基本上是一样的想法。宏只是隐藏样板。 – kotarak 2009-12-10 21:48:26

12

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”。这种行为会更灵活,但是不太方便(主叫方必须检查::看不到它的工作,而不仅仅是零),所以也许它会作为一个可选的第二个参数来实现最佳:

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

+1的错误结果,因为没有使用loop/recur。 – rplevy 2011-03-09 05:15:55

+1

可能,如果你得到了Error(Throwable的后裔)之一,你不想重试... – oshyshko 2013-12-13 03:10:58

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) 

将打印 “尝试” 3次,然后通过零扔鸿沟:

(try-times 3 (println "trying") (/ 1 0)) 
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)))))))) 
1

一个try-times宏是优雅的,但对于一个一次性的,只是拉你whentry块:

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