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!`调用,这是我的代码中唯一的一次突变。
抱歉。我不明白。你有机会调整你上面的回应,以便清楚地说明你的意思吗?
以下是我试图描述的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 次的循环来实现,每次迭代都进行一次带 catch 的 try 操作来捕获您想要触发的异常。欢迎在此处分享您已尝试的代码片段或从此处链接它们。

我不记得是否可以在循环内的 try 中进行 recur,但如果不能的话,您也可以在循环应该再次进入时,使整个 try 表达式返回一个独特的可区分的 'sentinel' 值,其中 'sentinel' 值简单地意味着“您知道您尝试调用的函数永远不会返回的一些值”。在 Clojure/Java 上,一个新分配的 (Object.) 实例通常满足这个属性。

...