请分享您的看法,参加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。我还是希望能找到一种不依赖于局部变量的方法来做这件事。不确定这样做是否符合函数式编程的观点。

我觉得可能会通过向集合中添加东西来计数更好。但这似乎意味着我会创建与你提出的相当的状态。
请注意,我在代码示例中仅使用可变的volatile对象是为了保存原始抛出的异常。如果能接受在所有用例中忘记原始异常,那么可以将sentinel的赋值改变为`(Object.)`(java.lang.Object类的一个新分配的实例),并取消`vreset!`调用,这是我代码中唯一的突变操作。
抱歉,我没有明白。能请你调整一下上面的回答,让我明白你的意思吗?
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
by

你希望执行的操作应该可以使用最多迭代3次循环的 loop,每个迭代都做一次带有 catchtry 来捕获你想再次尝试抛出的异常。你可以在这里共享你尝试过的代码片段,或者从这里链接到它们。

我不记得你能否在 loop 内部进行 recur 操作,但如果不行,你可以在循环再次尝试时,使整个 try 表达式返回一个独特的可区分的 'sentinel' 值,其中 'sentinel' 值简单地说就是“一个你知道你尝试调用的函数永远不会返回的值”。在 Clojure/Java 上 freshly allocated (Object.) 实例通常能满足这个属性。

...