retrying something 3 times before throwing an exception - in clojure

社会主义新天地 提交于 2019-12-21 02:52:14

问题


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 something so simple in Python is so hard in Clojure. I think the difficulty is because Clojure is a functional programming language and thus is 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)))))))

It is very ugly, and worse, it doesn't work as expected. The for loop is actually evaluated fully instead of lazily (I realized this when I put a println inside).

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


回答1:


Similar to Marcyk's answer, but no macro trickery:

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

Slightly complicated because you can't recur inside a catch clause. Note that this takes a function:

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



回答2:


Here's one approach:

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

Example from the REPL:

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

(Passing 2 to retry asks expr to be retried twice it if fails the first time round, for a total of three attempts. Three :foos are printed, because the println occurs before the throw in the do form passed to retry. The final ; Evaluation aborted. means an exception was thrown.)

Also, about the for loop from your snippet:

If you try looping over a longer range (replace (range 3) with (range 10), say), the output will end after i reaches 3. Also, if you put in a println before the form which throws the exception, it will of course print out whatever you pass to it; if the println occurs after the exception-throwing form, there will be no printout. In any case, at most three calls to println will be executed (assuming an exception is thrown on every iteration).




回答3:


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



回答4:


You can do like this:

(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 could use the function as such:

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

This would try at most three times to take a positive number at random from the vector and if it doesn't succeed, throws an exception.



来源:https://stackoverflow.com/questions/12068640/retrying-something-3-times-before-throwing-an-exception-in-clojure

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!