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。我还是希望能找到一种方法在不使用局部变量的情况下实现它。不清楚这是否从函数式编程的角度来说是在作弊。

我觉得可能通过向集合中添加内容来计数可能会更好。但似乎我还是会使用它来创建与你所建议的等价的状态。
请注意,在我的代码示例中,我之所以使用一个可变的易失性对象,只是为了保存最初抛出的异常。如果您在所有使用场景中都可以忽略原始异常,那么您可以将哨兵对象赋值改为`(Object.)`(Java.lang.Object类的新分配实例),并消除`vreset!`调用,这仅是我代码中唯一一次修改。
抱歉。我不明白。您能否稍微调整一下上面的答复,以展示您的意思?
以下是我想描述的函数retry-n-times的变种。再次强调,这个版本假设你从未想过保留调用抛出的异常。

(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中递归,但即使不能,你也可以让整个try表达式返回一个独特的可区分的'sentinel'值,如果循环应该重新开始的话,这里的'sentinel'值就是一个你不能期望要调用的函数返回的任何值。通常情况下,Clojure/Java上的一个新的localStorage实例可以满足这个特性。

...