2024 Clojure调查问卷!分享您的想法。

欢迎!请查看关于页面,了解有关此信息的一些更多信息。

0
Clojure

我是一个新手,试图做一件应该简单(可能已经很简单)的事儿。

如果函数抛出特定的异常,我想重试它。如果函数继续失败(连续三次抛出异常),我希望停止重试并抛出我自己抛出的新异常。

我尝试过使用looprecur,但无法使其工作。

有没有好主意?

2 个答案

+1

选中答案
 
最佳答案

以下代码是在Clojure 1.10.1 REPL中简要测试的,(以下是测试后的代码):

(def call-count (atom 0))

(defn throws-every-other-time [x]
  (let [new-count (swap! call-count inc)]
    (if (zero? (mod new-count 2))
      (assert false)  ;; throws java.lang.AssertionError
      (* 2 x))))

(throws-every-other-time 17)
;; Try this in a REPL and you see it throws on every other call

(defn retry-n-times [f n]
  (let [sentinel (volatile! nil)]
    (loop [i 0]
      (if (= i n)
        ;; Here is where you would throw your own desired exception.
        ;; In this example, I am just returning a map.
        {:call-count i, :failed-every-time true}
        ;; No println necessary - just nice to see what is going on
        ;; when testing a new function like this.
        (let [_ (println "Call (f) try" (inc i) "...")
              ret (try
                    (f)
                    (catch java.lang.AssertionError e
                      (vreset! sentinel e)
                      sentinel))]
          (if (identical? ret sentinel)
            (recur (inc i))
            ret))))))

(retry-n-times (fn [] (throws-every-other-time 17)) 3)
如果您不想自己实现,可以使用一些熔断器库。Diehard库基本具有以下功能:https://github.com/sunng87/diehard 他们的readme中的第一个例子就是这样做的。
这工作得很好,Andy。我还是希望能找到一种不用使用局部变量的方法来做的。不知道这从函数式编程的角度来看是否可以算作是作弊。

我觉得可能通过向集合中添加东西来计数会更好。但看起来我还是会使用它来创建你所提议的状态等价物。
请注意,在我的代码示例中,我使用可变易变对象的原因是为了保存原始抛出的异常。如果您在所有情况下都可以忘记原始异常,那么您可以将sentinel分配的值改为`(Object.)`(java.lang.Object类的一个新分配实例),并删除`vreset!`调用,这是我代码中唯一的突变发生。
抱歉,我没有明白您的意思。您能否稍微调整一下您上面的回答,以展示您的意思?
以下是我想描述的函数重试-n-次的变化形式。同样,这个版本假定你不关心保留调用的异常(f)。

(defn retry-n-times [f n]
  (let [sentinel (Object.)]
    (loop [i 0]
      (if (= i n)
        ;; 在这里,你可以抛出你想要的异常。
        ;; 在此示例中,我只是返回一个映射。
        {:call-count i, :failed-every-time true}
        ;; 无需 println - 但在测试新函数时看到正在发生的事情是很好的
        ;; 状态。
        (let [_ (println "调用 (f) 尝试" (inc i) "...")
             ret (try
                    (f)
                    (catch java.lang.AssertionError e
                      sentinel))]
          (if (identical? ret sentinel)
            (recur (inc i))
            ret))))))
0

你应该能够使用最多迭代3次的loop来实现,每次迭代内部进行try和catch捕获你想尝试重试的异常。欢迎在此分享你尝试的代码片段或从其他地方链接过来。

我记不清是否可以在loop内的try中使用recur了,但即使不能,你可以在循环再次运行时使整个try表达式返回一个独特的可区分的'sentinel'值,其中'sentinel'值仅指“一个你知道你试图调用的函数永远不会返回的值”。在Clojure/Java中,一个新分配的(Object.)实例通常满足这个属性。

...