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。我仍然希望找到一个不使用本地变量的方式来完成它。不确定这对于纯函数(FP)来说是否可能。

我想通过向集合中添加内容来计数可能更好。但似乎我还是在做你提出的状态的等效操作。
请注意,在我的代码示例中,我之所以使用了可变的不稳定的对象,仅仅是为了保存原始抛出的异常。如果在你所有的用例中都可以忽略原始异常,那么你可以将sentinel分配的值改为`(
抱歉。我不明白。你能否修改你上面的回答来展示你的意思?
这里是我想要描述的函数 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 次的循环来实现,每次迭代在循环内部进行一次 trycatch 来捕获你想要重新抛出的异常。欢迎在此分享你尝试过的代码片段,或者从此处链接到它们。

我不确定是否可以在循环内部的 try 中进行 recur,但如果不行,你可以使整个 try 表达式返回一个独特的可区分的 'sentinel' 值,如果需要循环再次周转,其中 'sentinel' 值只是指“你知道你尝试调用的函数永远不会返回的某个值”。在 Clojure/Java 中,新分配的 (Object.) 实例通常满足这个属性。

...