2009-12-10 2 views
37

Я пытаюсь выполнить func несколько раз, прежде чем отказаться от исключений. Но это недействительно в Clojure, чтобы вернуться из блока catch. Как это можно достичь?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

+0

На самом деле это то же самое решение, что и предложенный плакат. Но макросы упрощают работу в общем случае. Макросы - это функция убийцы любого варианта lisp. –

+0

Это не совсем то же самое решение. Предложение плаката не улавливает возвращаемое значение блока, и если бы он сделал блок, он не смог бы вернуть нуль. Также исключения проглатываются. Но вы правы: это в основном та же идея. Макросы просто скрывают шаблон. – kotarak

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

И оставить примерочные разы макроса, как это.

Если вы хотите разрешить thunk возвращать нуль, вы можете оставить пару let/when и позволить :: fail представлять «функция сбой n раз», а nil означает «функция возвратила нуль». Такое поведение было бы более гибким, но менее удобен (абонент должен проверить :: не увидеть, если он работал, а не просто ноль), поэтому, возможно, лучше всего было бы реализовать в качестве необязательного второго параметра:

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

+1 для использования цикла/recur. – rplevy

+1

, вероятно, вы не хотите повторять попытку, если у вас есть одна из ошибок (потомок Throwable) ... – oshyshko

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

A 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)))) 
Смежные вопросы