2012-08-22 19 views
5

no sé cómo implementar esta pieza de código Python en Clojurede volver a intentar algo 3 veces antes de lanzar una excepción - en clojure

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

Me pregunto por qué algo tan simple en Python es tan duro en Clojure. Creo que la dificultad radica en que Clojure es un lenguaje de programación funcional y, por lo tanto, no es adecuado para una tarea tan imperativa. Este es mi intento:

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

Es muy feo, y lo que es peor, no funciona como se esperaba. El bucle for es realmente evaluado completamente en lugar de perezoso (me di cuenta de esto cuando puse un println dentro).

Si alguien tiene una mejor idea para implementar eso, por favor ilumíname.

+3

posible duplicado de [Clojure: ¿Cómo recurrir con la excepción?] (Http: // stackoverflow.com/questions/1879885/clojure-how-to-recur-upon-exception) – amalloy

Respuesta

10

Similar a la respuesta de Marcyk, pero sin engaños macro:

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

poco complicada porque no se puede recur dentro de una cláusula de catch. Tenga en cuenta que esto tiene una función:

(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

Un pequeño comentario sobre el comentario en el ejemplo - como está escrito, esto imprimirá hasta cuatro 'foo's, no tres, cuando se pasa un argumento inicial de 3. ('f' se aplica a' args' antes de la verificación cero). –

+0

Sin embargo, podría llamarse a sí mismo desde dentro de un catch (no stack overflow para valores pequeños de * tries *). Pero me gusta tu solución. –

+0

Sí, eso debería ser un máximo de cuatro: los intentos deberían llamarse * re * tries en este caso. –

7

Así es uno de los enfoques:

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

Ejemplo de la REPL:

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

(Pasando 2-retry pide expr que reintentar el doble que si falla el primer tiempo de ida, para un total de tres intentos. Se imprimen tres :foo s, porque println aparece antes del throw en el formulario do pasado al retry. La final ; Evaluation aborted. significa que se produce una excepción)

También, acerca de la for asa de su fragmento:.

Si intenta bucle con un alcance mayor (sustituir (range 3) con (range 10), por ejemplo), la salida terminará después i llega a 3. Además, si coloca un println antes del formulario que arroja la excepción, por supuesto imprimirá lo que le pase; si se produce el println después del formulario de lanzamiento de excepción, no habrá impresión. En cualquier caso, se ejecutarán como mucho tres llamadas a println (suponiendo que se lanza una excepción en cada iteración).

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

Usted puede hacer así:

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

Posteriormente, se podría utilizar la función como tal:

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

Esto sería intentar por lo la mayoría de las veces para tomar un número positivo al azar del vector y si no tiene éxito, arroja una excepción.

Cuestiones relacionadas