5

我不知道如何在 Clojure 中实现这段 Python 代码

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

我想知道为什么 Python 中如此简单的东西在 Clojure 中却如此困难。我认为困难在于 Clojure 是一种函数式编程语言,因此不适合这种命令式任务。这是我的尝试:

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

它非常难看,更糟糕的是,它没有按预期工作。for 循环实际上是完全评估而不是延迟评估(当我在里面放一个 println 时我意识到了这一点)。

如果有人有更好的想法来实现它,请赐教。

4

5 回答 5

12

类似于 Marcyk 的答案,但没有宏诡计:

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

有点复杂,因为您不能recurcatch子句中。请注意,这需要一个函数:

(retry 3 (fn [] 
          (println "foo") 
          (if (zero? (rand-int 2))
              (throw (Exception. "foo"))
              2)))
=>
foo ;; one or two or three of these
foo
2
于 2012-08-22T08:15:11.277 回答
7

这是一种方法:

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

(如果第一次失败,则传递2toretry要求重试两次,总共尝试三次。打印三个 s,因为在传递给的表单中发生之前。最后意味着抛出异常。)expr:fooprintlnthrowdoretry; Evaluation aborted.

另外,关于for您的代码段中的循环:

如果您尝试在更长的范围内循环(例如替换(range 3)(range 10)),则输出将在i到达之后结束3。此外,如果您在println引发异常的表单之前输入 a,它当然会打印出您传递给它的任何内容;如果println发生在异常抛出表单之后,则不会打印输出。在任何情况下,最多println将执行三个调用(假设每次迭代都会引发异常)。

于 2012-08-22T08:09:55.410 回答
0
(cond (every? nil? (for [x (range (inc retry)) :while (not @tmp-doc)]
 ...do sth) )                  
;all failed
:else
;at least one success
于 2013-12-26T19:55:01.103 回答
0

根据这个问题的答案重试睡眠和另一个实现:

(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)))
于 2021-06-17T11:00:02.833 回答
0

你可以这样做:

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

然后你可以这样使用这个函数:

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

这将尝试最多三次从向量中随机取一个正数,如果不成功,则抛出异常。

于 2017-03-07T13:48:54.220 回答