2

I have a loop that retries creating a socket when there is an exception. I want to return that socket if there is a successful creation of that socket, otherwise retry.

I want the loop to evaluate to the thing inside the loop, the number 6 in this example, my code resembles the following:

(loop []                                                                                                                                                                           
  (when-not
    (try                                                                                                                                                                                                                                                                                                                  
       6                                                                                                                                     

     (catch Exception e                                                                                                                                                                  
       (.println *err* (str e))                                                                                                                                                           
      false                                                                                                                                                                              
     )
   )                                                                                                                                                                                  
 (recur)                                                                                                                                                                             
 )
)  

This currently returns nil. How do I make this block return 6? (In my real code, the code is a socket creation and can throw an exception)

jepsen.etcdemo=>     (loop []                                                                                                                                                                           
            #_=>       (when-not
            #_=>         (try                                                                                                                                                                                                                                                                                                                  
            #_=>            6                                                                                                                                     
            #_=>     
            #_=>          (catch Exception e                                                                                                                                                                  
            #_=>            (.println *err* (str e))                                                                                                                                                           
            #_=>           false                                                                                                                                                                              
            #_=>          )
            #_=>        )                                                                                                                                                                                  
            #_=>      (recur)                                                                                                                                                                             
            #_=>      )
            #_=>     )  
nil
Samuel Squire
  • 127
  • 3
  • 13
  • If you want the value `6`, why don't you just replace *the entire loop* by the value `6`? This question lacks context. What *algorithm* are you trying to implement? – Rulle May 12 '23 at 15:54
  • The code that is actually in the loop in place of the number value 6 is code that creates a Java socket, I want to return the Java socket of the successful connection and if there is an exception, retry creating the Java connection. In other words, we should retry if there is an exception, otherwise return the first successful result if there is a lack of exception. – Samuel Squire May 12 '23 at 15:57

3 Answers3

3

So apparently just putting a recur call in a catch block like the following would not compile as catch block is not a recur target:

(loop [] (try 6
           (catch Exception e 
                  (println e)
                  (recur))))

However we can fix it easily:

(loop []
  (or (try 6
           (catch Exception e 
                  (println e)
                  nil))
      (recur)))

Or in a more functional style:

(->>
 #(try 6
     (catch Exception e
             (println e)
             nil))
 (repeatedly)
 (some identity))
erdos
  • 3,135
  • 2
  • 16
  • 27
2

I wanted to expand erdos's answer, but couldn't edit it, so here's some information on why your example didn't work, and a couple of options for solving it with explanations.

when-not does not return the value it tests. (when x y) is equivalent to (if x y nil), and (when-not x y) is equivalent to (if (not x) y nil). when and when-not are macros that expand to if's returning nil in the second branch.

or as used in erdos's first example (after his edit), returns the first value that is logically true, if none of the values are logically true, the last value is returned. That's why (or x (recur)) gives you the value of x unless x is a falsey value, in which case (recur) is evaluated and its value is returned.

Erdos's second example uses an infinite lazy sequence to get the first non-nil result. (repeatedly f) returns a lazy sequence where every element is an evaluation of an invocation of f. That is, when an element is first retrieved, f is invoked and that returned as that element. (some identity c) gets the first logically true element in c. Note that some returns the value of the evaluation of the function passed as the first argument, so if the predicate function is more complex, you might use filter and first:

(->>
 #(try 6
     (catch Exception e
             (println e)
             nil))
 (repeatedly)
 (filter #(= % 6)
 (first)

This would only return a value if it is equal to 6. If you used the same predicate with some, you would get true instead of 6.

If you want to limit the number of retries, you can also pass the maximum number of tries to repeatedly:

(->>
 #(try 6
     (catch Exception e
             (println e)
             nil))
 (repeatedly 10)
 (some identity))

Would try at up to 10 times to get a connection, before giving up.

If you are more familiar with imperative languages, it might be easier to understand the following:

(loop []
  (if-let [conn (try 6
                 (catch Exception e 
                   (println e)
                   nil))]
    conn
    (recur)))

if-let is equivalent to a let with a single definition, and an if that uses that definition as its test. (if-let [x y] x "holy moly") is equivalent to (let [x y] (if x x "holy moly")). Especially if you want to do something more with the value when it is truthy, if-let and when-let are pretty nice syntactical sugar.

Sardtok
  • 449
  • 7
  • 18
0

Something along the lines of


(defn retry!
  [n f]
  (loop [counter 0
         result nil
         last-error nil]
    (cond
      result
      [result]
      (>= counter n)
      [nil last-error]
      :else
      (let [[result last-error] (try
                                  [(f)]
                                  (catch Exception e
                                    [nil e]))]
        (recur (inc counter)
               result
               last-error)))))


(retry! 2
        (fn []
          (throw (ex-info "lol" {}))))
; [nil #error "lol"]
(retry! 2
        (constantly :baz))
; [:baz]

Using the convention of always returning an array with the first value being the "success" one and the second being the error.

adz5A
  • 2,012
  • 9
  • 10