Repeat something 3 times before throwing an exception - in clojure

I don't know how to implement this piece of Python code in Clojure

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

I wonder why in Python something is so simple in Clojure. I believe that the difficulty is that Clojure is a functional programming language and therefore not suitable for such an imperative task. This is my attempt:

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

This is very ugly, and worse, it does not work as expected. The for loop actually computes completely instead of lazily (I figured this out when I inserted println inside).

If anyone has a better idea to implement this, please enlighten me.

+5
source share
4 answers

Like Marcyk's answer, but not macro definitions:

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

, recur catch. , :

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

:

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

REPL:

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

( 2 retry expr , , . :foo , println throw do, retry. ; Evaluation aborted. , .)

, for :

( (range 3) (range 10), ), , i 3. , println , , , , , ; println , . println ( , ).

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

You can do the following:

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

Then you can use the function as such:

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

This will work no more than three times in order to randomly get a positive number from the vector, and if this fails, throws an exception.

0
source

All Articles