2012-08-22 9 views
5

Ich weiß nicht, wie dieses Stück Python-Code in Clojureretrying etwas 3-mal vor Auslösen einer Ausnahme - in clojure

for i in range(3): 
    try: 
     ...... 
    except e: 
     if i == 2: 
      raise e 
     else: 
      continue 
    else: 
     break 

ich implementieren fragen, warum etwas so einfach in Python so hart in Clojure ist. Ich denke, die Schwierigkeit liegt darin, dass Clojure eine funktionale Programmiersprache ist und daher für eine solche imperative Aufgabe nicht geeignet ist. Dies ist mein Versuch:

(first 
    (remove #(instance? Exception %) 
    (for [i (range 3)] 
     (try (......) 
       (catch Exception e 
       (if (== i 2) 
        (throw e) 
        e))))))) 

Es ist sehr hässlich, und schlimmer noch, es nicht wie erwartet. Die for-Schleife wird tatsächlich vollständig und nicht träge ausgewertet (das habe ich festgestellt, als ich einen println hineingesteckt habe).

Wenn jemand eine bessere Idee hat, das zu implementieren, bitte erleuchte mich.

+3

mögliche Duplikate von [Clojure: Wie wiederhole ich eine Ausnahme?] (Http: // stackoverflow.com/questions/1879885/clojure-how-to-wiederkehren-auf-Ausnahme) – amalloy

Antwort

10

Ähnlich Marcyk Antwort, aber keine Makro Tricks:

(defn retry 
    [tries f & args] 
    (let [res (try {:value (apply f args)} 
       (catch Exception e 
        (if (= 0 tries) 
        (throw e) 
        {:exception e})))] 
    (if (:exception res) 
     (recur (dec tries) f args) 
     (:value res)))) 

etwas kompliziert, weil man nicht recur innerhalb einer catch Klausel. Beachten Sie, dass dies eine Funktion übernimmt:

(retry 3 (fn [] 
      (println "foo") 
      (if (== 0 (int (rand 2))) 
       (throw (Exception. "foo")) 
       2))) 
=> 
foo ;; one or two or three of these 
foo 
2 
+1

Ein kleiner Punkt Nitpick über den Kommentar in dem Beispiel - wie geschrieben, das wird bis zu vier 'foo's, nicht drei drucken, wenn ein Initialargument von 3 übergeben wird. ('f' wird auf' args' vor der Nullprüfung angewendet.) –

+0

Sie könnten sich jedoch von innen als catch bezeichnen (kein Stack-Überlauf für kleine Werte von * tries *). Aber ich mag deine Lösung. –

+0

Ja, das sollte bis zu vier sein - Versuche sollten in diesem Fall wahrscheinlich * re * probiert werden. –

7

Hier ist ein Ansatz:

(defmacro retry 
    "Evaluates expr up to cnt + 1 times, retrying if an exception 
    is thrown. If an exception is thrown on the final attempt, it 
    is allowed to bubble up." 
    [cnt expr] 
    (letfn [(go [cnt] 
      (if (zero? cnt) 
       expr 
       `(try ~expr 
        (catch Exception e# 
         (retry ~(dec cnt) ~expr)))))] 
    (go cnt))) 

Beispiel aus dem REPL:

user> (retry 2 (do (println :foo) (throw (RuntimeException. "foo")))) 
:foo 
:foo 
:foo 
; Evaluation aborted. 

(Passing 2-retryexpr fragt zweimal es erneut versucht werden, wenn rund um das erste Mal fehlschlägt, für insgesamt drei Versuche: Drei :foo s werden gedruckt, weil die println vor der throw in der do Form auftritt, die anübergeben wird. Die endgültige ; Evaluation aborted. Mittel wurde eine Ausnahme ausgelöst)

Auch über die for Schleife von Ihrem Schnipsel.

Wenn Sie über einen längeren Bereich versuchen Looping (ersetzen (range 3) mit (range 10), sagen), wird die Ausgabe am Ende nach i erreicht 3. Auch, wenn Sie eine println vor dem Formular, das die Ausnahme wirft, wird es natürlich ausdrucken, was auch immer Sie daran übergeben; Wenn die println nach dem Formular zum Auswerfen der Ausnahme auftritt, wird kein Ausdruck ausgegeben. In jedem Fall werden höchstens drei Aufrufe an println ausgeführt (vorausgesetzt, dass bei jeder Iteration eine Ausnahme ausgelöst wird).

0
(cond (every? nil? (for [x (range (inc retry)) :while (not @tmp-doc)] 
...do sth))     
;all failed 
:else 
;at least one success 
0

Sie können wie folgt tun:

(defn retry 
    "Tries at most n times, return first try satisfying pred or nil" 
    [times pred? lazy-seq] 
    (let [successful-trial (drop-while (complement pred?) (take times lazy-seq))] 
    (if (empty? successful-trial) 
     nil 
     (first successful-trial)))) 

Dann könnten Sie die Funktion als solche verwendet werden: bei

(when-not (retry 3 pos? (repeatedly #(rand-nth [-1 -2 -3 2 1])) 
    (throw (Exception. "my exception message")) 

Dies würde versuchen, die meisten drei Male, um eine positive Zahl zufällig aus dem Vektor zu nehmen, und wenn es nicht erfolgreich ist, löst eine Ausnahme aus.

Verwandte Themen