Fork me on GitHub
#missionary
<
2024-02-06
>
Ketan08:02:25

I was reading through the wiki pages and I'm wondering: The "Iterative queries" page mentions that recursively building successive flows is a bad idea since it might blow the stack. In the "Retry with backoff" example:

(defn backoff [request delays]
  (if-some [[delay & delays] (seq delays)]
    (m/sp
      (try (m/? request)
           (catch Exception e
             (if (-> e ex-data :worth-retrying)
               (do (m/? (m/sleep delay))
                   (m/? (backoff request delays)))
               (throw e)))))
    request))
we're limiting the number of delays to 5 so this is not a concern here. But would it not be better to explicitly use a loop-`recur` solution here too? That would allow delays to be unbounded.

leonoel10:02:53

yes, absolutely

leonoel10:02:48

(defn backoff [request delays]
  (m/sp
    (loop [delays (seq delays)]
      (if-some [response (try (m/? request)
                              (catch Exception e
                                (when-not (-> e ex-data :worth-retrying)
                                  (throw e))))]
        response
        (if-some [[delay & delays] delays]
          (do (m/? (m/sleep delay)) (recur delays))
          (throw (ex-info "Too many attempts." {:request request})))))))

Ketan10:02:36

Thanks for clarifying!

Ketan13:02:26

Just checking - is (m/?> m/none) equivalent to (m/amb)in an ap block? For context I was looking at this example in the rxjava comparison:

(defn feed->flow [feed]
  (let [mbx (m/mbx)]
    (.register feed (reify SomeListener
                      (priceTick [event] (mbx [:val event]) (when (.isLast event) (mbx [:done])))
                      (error [e] (mbx [:err e]))))
    (m/ap (loop [[t v] (m/? mbx)]
            (case t
              :val (if (m/?> (m/seed [true false])) v (recur (m/? mbx)))
              :err (throw e)
              :done (m/?> m/none))))))

leonoel13:02:56

yes. also (m/?> (m/seed [true false])) is equivalent to (m/amb true false) - the latter is sugar over the former.

Ketan13:02:44

Ah okay that makes a lot of sense. amb did seem like something that could be recreated by the other primitives.

leonoel14:02:35

in fact the entire conditional can be rewritten as (m/amb v (recur (m/? mbx)))

namenu13:02:14

Just curious. Where are those abbreviations come from? ap, amb, absolve, etc.

leonoel13:02:08

amb is short for "ambiguous" evaluation - popularized by SICP (but predates it) https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/full-text/book/book-Z-H-28.html#%_sec_4.3 ap is "ambiguous process" - an evaluation context that allows ambiguous evaluation absolve & attempt are inspired by ZIO https://zio.dev/1.0.18/overview/overview_handling_errors/

👍 3
Ketan16:02:02

Is there a way to cancel one task from another? I was thinking something like:

Ketan16:02:51

On line 9, I'm trying to cancel the sleep. I know there's ways around this (like race on the sleep with something else), but is there a way to explicitly cancel a task? If not, is there a reason? I'm assuming it might break some constructs...

leonoel16:02:53

It's not possible because the composition model never exposes the process handle to the user. (m/sleep delay) is a pure function, it has no effect. The sleep is performed on (m/? wait-for-next), so only the parent process (i.e. the outer sp) has access to the cancellation signal

Ketan17:02:52

Got it. So only the parent (i.e. supervisor) can do the cancellation, and that means using one of the operators as a cancellation strategy.

leonoel17:02:29

yes, it's the only way

Ketan17:02:43

Btw the definition of timeout in the code is very pretty