Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: How to to recur upon exception?

Tags:

clojure

I am trying to execute a func several times before giving up upon exceptions. But it is not valid in Clojure to recur from catch block. How can this be achieved ?

(loop [tries 10]   (try     (might-throw-exception)     (catch Exception e       (when (pos? tries) (recur (dec tries))))))  java.lang.UnsupportedOperationException: Cannot recur from catch/finally  

The best I could find is the following clumsy solution (wrapping in func and calling it)

(defn do-it []   (try     (might-throw-exception)     (catch Exception e nil)))  (loop [times 10]   (when (and (nil? (do-it)) (pos? times))     (recur (dec times)))) 
like image 309
GabiMe Avatar asked Dec 10 '09 09:12

GabiMe


2 Answers

Macros are calling...

How about this:

(defn try-times*   "Executes thunk. If an exception is thrown, will retry. At most n retries   are done. If still some exception is thrown it is bubbled upwards in   the call chain."   [n thunk]   (loop [n n]     (if-let [result (try                       [(thunk)]                       (catch Exception e                         (when (zero? n)                           (throw e))))]       (result 0)       (recur (dec n)))))  (defmacro try-times   "Executes body. If an exception is thrown, will retry. At most n retries   are done. If still some exception is thrown it is bubbled upwards in   the call chain."   [n & body]   `(try-times* ~n (fn [] ~@body)))
like image 179
kotarak Avatar answered Sep 24 '22 08:09

kotarak


kotarak's idea is the way to go, but this question tickled my fancy so I'd like to provide a riff on the same theme that I prefer because it doesn't use loop/recur:

(defn try-times* [thunk times]   (let [res (first (drop-while #{::fail}                                (repeatedly times                                            #(try (thunk)                                                  (catch Throwable _ ::fail)))))]     (when-not (= ::fail res)       res))) 

And leave the try-times macro as it is.

If you want to allow the thunk to return nil, you can drop the let/when pair, and let ::fail represent "the function failed n times", while nil means "the function returned nil". This behavior would be more flexible but less convenient (the caller has to check for ::fail to see if it worked rather than just nil), so perhaps it would be best implemented as an optional second parameter:

(defn try-times* [thunk n & fail-value]   (first (drop-while #{fail-value} ...))) 
like image 33
amalloy Avatar answered Sep 26 '22 08:09

amalloy