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。我还是希望能找到一种不使用局部变量来实现的方法。不确定这从函数式编程的角度来看是否算作弊。

我觉得可能通过向集合中添加项来计数可能更好。但看起来我还是要用它来创建与您所提出的状态等效的状态。
by
请注意,在我的代码示例中,我之所以使用可变的不稳定的对象,只是为了保存抛出的原始异常。如果没有必要记住原始异常,则可以将哨兵的值更改为`(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)
        ;; 这里是抛出你自己的期望异常的位置。
        ;; 在这个例子中,我只是返回一个映射。
        {:调用的次数 i, :每次都失败 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 实现,每次迭代中包含 try 和 catch 以捕获想要再次尝试抛出的异常。您欢迎在此分享您尝试过的代码片段,或从此处链接到它们。

我手头没有记得是否可以在 loop 中的 try 内部使用 recur,但即使不行,您也可以使整个 try 表达式返回一个在循环再次进行时可以独特区分的 'sentinel' 值,其中 'sentinel' 值仅指“你知道你试图调用的函数永远不会返回的某个值”。在 Clojure/Java 上分配的 (Object.) 实例通常满足此属性。

...