2009-12-10 4 views
37

J'essaye d'exécuter une fonction plusieurs fois avant d'abandonner les exceptions. Mais il n'est pas valide à Clojure de se reproduire à partir du bloc catch. Comment cela peut-il être réalisé?Clojure: Comment se reproduire lors d'une exception?

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

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

Le mieux que je pouvais trouver est la solution maladroite suivante (emballage Func et de l'appeler)

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

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

Répondre

41

macros appellent ...

Que diriez-vous ceci:

(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

C'est une bonne solution. Je voudrais l'ajouter à clojure.contrib ou quelque chose. – GabiMe

+0

C'est en fait la même solution que celle proposée par l'affiche. Mais les macros le rendent plus facile à faire dans le cas général. Les macros sont la caractéristique de tueur de toute variante de lisp. –

+0

Ce n'est pas exactement la même solution. La suggestion de l'affiche n'attrape pas la valeur de retour du bloc, et si c'était le cas, le bloc ne serait pas capable de revenir à zéro. De plus, les exceptions sont avalées. Mais vous avez raison: c'est fondamentalement la même idée. Les macros ne font que masquer le passe-partout. – kotarak

12

L'idée de kotarak est la voie à suivre, mais cette question a chatouillé mon envie alors je voudrais fournir un riff sur le même thème que je préfère parce qu'elle ne pas utiliser la boucle/RECUR:

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

et laisser les essayages fois macro comme il est. Si vous voulez permettre au thunk de renvoyer zéro, vous pouvez supprimer la paire let/when, et laisser :: fail représenter "la fonction failed n fois", tandis que nil signifie "la fonction retournée nil". Ce comportement serait plus souple mais moins pratique (l'appelant doit vérifier :: ne vois pas si cela a fonctionné plutôt que nulle), il serait peut-être mieux mis en œuvre comme second paramètre:

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

+1 pour ne pas utiliser loop/recur. – rplevy

+1

probablement, vous ne voulez pas réessayer si vous avez un de Error (descendant de Throwable) ... – oshyshko

3

ma proposition:

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

imprimera "aucune erreur ici" une fois:

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

imprimera "essayer" 3 fois, puis jeter par zéro Divide:

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

Une autre solution, sans macro

(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

Une macro try-times est élégant, mais pour une seule fois, il suffit de tirer votre when hors du bloc 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)))) 
Questions connexes