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中的第一个示例就是这样做。
by
这对Andy来说效果很好。我还是希望找到一种方法来做这件事,而无需借助相当于局部变量的东西。不清楚这从FP(函数式编程)的角度来看是否算作弊。

我原以为通过向集合中添加东西来计数可能更好。但这似乎最终我会用这种方式创建与您提出的状态等效的状态。
by
请注意,在我的代码示例中,我之所以使用可变的volatile对象,只是为了保存最初抛出的异常。如果所有情况下都可以忽略原始异常,则可以将sentinel分配的值更改为`(Object.)`(java.lang.Object类的新分配的实例),并消除`vreset!`调用,这是我的代码中唯一的突变事件。
by
抱歉,我不太明白。您能稍微调整一下您上面的回答,让我明白您的意思吗?
by
以下是我试图描述的retry-n-times函数的变体。同样,这个版本假设您从不关心保留调用(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`,每次迭代在循环内部执行带有 `catch` 的 `try`,以捕获您想要再次尝试抛出的异常。您可以在此处分享您已尝试的代码片段,或链接到它们。

我并不清楚您是否可以在 `loop` 中的 `try` 内部执行 `recur`,但如果不行的话,您也可以使整个 `try` 表达式返回一个独特的区别性值 'sentinel',如果循环需要再次运行,其中 'sentinel' 值简单意味着 "某个您知道您尝试调用的函数永远无法返回的值"。在一个新分配的 Clojure/Java 中的 `(Object.)` 实例通常会满足这个属性。

...