2024年Clojure状态调查中分享你的想法!

欢迎!请查看关于页面以了解更多关于这是如何运作的信息。

0
Clojure

我是个新手,试图做些本应(很可能确实是)简单的事情。

如果函数抛出特定异常,我想重试该函数。如果函数持续失败(连续抛出该异常三次以上),我想停止重试并抛出我自己的新异常。

我尝试了looprecur,但无法让它工作。

有什么建议吗?

2 个答案

+1
by Andy Fingerhut 选定的答案
 
最佳答案

以下是一些简略测试过的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!`调用,这是我的代码中唯一发生变异的地方。
对不起。我没有理解你的意思。你能调整你上面的回答让我看出你的意图吗?
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 来实现,每次迭代执行一个包含 catch 的 try 以捕获你想尝试再次的异常。你可以在此处分享你尝试过的代码片段,或从此处链接。

我不记得是否可以在 loop 内部进行 recur,即使在 inside 的 try 中,你也可以使整个 try 表达式如果循环应该再次进行,返回一个独特的可区分的“哨兵”值,其中“哨兵”值只是指“你应该知道你尝试调用的函数永远不会返回一些值”。在 Clojure/Java 中,一个新分配的 (Object.) 实例通常满足这个属性。

...