5

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.

user1442023
  • 73
  • 1
  • 6
  • 3
    possible duplicate of [Clojure: How to to recur upon exception?](http://stackoverflow.com/questions/1879885/clojure-how-to-to-recur-upon-exception) – amalloy Aug 22 '12 at 08:23

5 Answers5

12

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
tkocmathla
  • 901
  • 11
  • 24
Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
7

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

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
0
(cond (every? nil? (for [x (range (inc retry)) :while (not @tmp-doc)]
 ...do sth) )                  
;all failed
:else
;at least one success
0

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.

svelten
  • 1,429
  • 13
  • 12
0

Retry with sleep and another implementation based on this question's answers:

(defn retry [retries sleep-ms func url options]
  (let [{:keys [status headers body error] :as resp} @(func url options)]
    (if error
      (do
        (log/error (str "Retry " retries " returned error: " e))
        (Thread/sleep sleep-ms)
        (if (= retries 1)
          resp
          (retry (dec retries) sleep-ms func url options)))
      resp)))
surfealokesea
  • 4,971
  • 4
  • 28
  • 38