2009-12-10 16 views
37

Próbuję wykonać func kilka razy przed rezygnacją z wyjątków. Ale nie jest ważne w Clojure, aby powrócić z bloku catch. Jak można to osiągnąć?Clojure: Jak powtórzyć po wyjątku?

(loop [tries 10] 
    (try 
    (might-throw-exception) 
    (catch Exception e 
     (when (pos? tries) (recur (dec tries)))))) 

java.lang.UnsupportedOperationException: Cannot recur from catch/finally 

Najlepszą udało mi się znaleźć jest następujące rozwiązanie niezdarny (zawijania w func i wzywającą go)

(defn do-it [] 
    (try 
    (might-throw-exception) 
    (catch Exception e nil))) 

(loop [times 10] 
    (when (and (nil? (do-it)) (pos? times)) 
    (recur (dec times)))) 

Odpowiedz

41

Makra dzwonisz ...

Jak o tym:

(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

Jest dobre rozwiązanie. Dodałbym go do clojure.contrib lub coś podobnego. – GabiMe

+0

To właściwie to samo rozwiązanie, które sugerował plakat. Ale makra ułatwiają to w ogólnym przypadku. Makra są zabójcą każdego wariantu seplenienia. –

+0

To nie jest dokładnie to samo rozwiązanie. Sugestia plakatu nie przechwytuje wartości zwracanej bloku, a jeśli tak, blok nie będzie w stanie zwrócić zero. Również wyjątki są połknięte. Ale masz rację: to w zasadzie ten sam pomysł. Makra po prostu ukrywają płytę główną. – kotarak

12

Pomysł kotaraka jest do zrobienia, ale to pytanie zafascynowało moją wyobraźnię, więc chciałbym podać riff na ten sam temat wolę, ponieważ nie używa pętli/recur:

(defn try-times* [thunk times] 
    (let [res (first (drop-while #{::fail} 
           (repeatedly times 
              #(try (thunk) 
               (catch Throwable _ ::fail)))))] 
    (when-not (= ::fail res) 
     res))) 

I pozostaw makro próbne w niezmienionym stanie.

Jeśli chcesz, aby thunk powrócił do zera, możesz upuścić parę let/when, a let :: fail oznacza "funkcja zakończona niepowodzeniem n razy", podczas gdy zero oznacza "funkcja zwrócona zero". Takie zachowanie byłoby bardziej elastyczne, ale mniej wygodne (dzwoniący musi sprawdzić :: nie widzi, czy to zadziałało, a nie tylko zero), więc może byłoby najlepiej realizowane jako opcjonalny drugi parametr:

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

+1 dla nieużywania pętli/recur. – rplevy

+1

Prawdopodobnie nie chcesz ponowić próby, jeśli masz jeden z Error (potomek Throwable) ... – oshyshko

3

Moja propozycja:

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

wypisze "błędów tu" raz:

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

wypisze "stara" 3 razy, a następnie rzucać dzielenie przez zero:

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

Jeszcze rozwiązanie, bez makro

(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

A try-times makro jest elegancki, ale dla jednorazowych, po prostu wyciągnąć when z bloku 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)))) 
Powiązane problemy