Fork me on GitHub
#missionary
<
2023-09-24
>
alex15:09:12

Hi there, I'm trying to build a better intuition around the initialization of a flow, how values travel through it, and the behavior of m/reduce as the final step in the flow. In the following flow, I'm observing a few things that I'm hoping to get clarification on. The output of the flow along with the questions are towards the bottom of the code block:

(defn- ->counter
"Increments a counter and emits the current counter value every 1s"
  []
  (let [counter (atom 0)]
    (->> (m/observe
          (fn [emit!]
            (let [emit! #(do (js/console.warn :msg %) (emit! %))
                  interval (js/setInterval #(do (emit! @counter)
                                                (swap! counter inc))
                                           1000)]
              (fn [] (js/clearInterval interval))))))))

(defn use-flow
"This flow is running in the context of a React component. Mostly just implementation detail 
for creating and destroying the flow"
  [flow {:keys [on-success on-error]}]
  (rum/use-effect!
   (fn []
     (let [cancel-flow (flow on-success on-error)]
       ;; missionary returns a thing that implements IFn to
       ;; cancel the flow but that is not correctly
       ;; understood by React as a hook cleanup function
       (fn [] (cancel-flow))))
   []))

(defn counter-listener
  []
  (use-flow
   (->> (->counter)
        ((fn [>x]
           (m/ap
            (let [change (m/?= >x)]
              (js/console.warn :change change)
              change))))
        (m/reductions (fn [acc val]
                        (js/console.warn :val val)
                        (conj acc val)))
        (m/eduction (remove (fn [vals] (js/console.warn :empty? vals) (empty? vals))))
        (m/reduce (fn [_ new]
                    (js/console.warn :reduce new))))
   {:on-success (fn [success] (js/console.log "done" success))
    :on-error (fn [err] (js/console.error err))}))

;; With no `initial-value` passed to `m/reductions`, this logs the following:
=> :val undefined ;; where does this `undefined` value come from?
=> :empty? (nil)
=> :reduce undefined ;; why does the first `reduce` receive an undefined?
=> :reduce (nil)
=> :val 0
=> :empty? (0 nil)
=> :reduce (0 nil)

;; with initial value of [] in `m/reductions`, this logs the following:
=> :empty? [] ;; interestingly :val doesn't get logged with an undefined here (like above)
=> :reduce undefined
=> :val 0
=> :empty? [0]
=> :reduce [0]

leonoel16:09:04

m/reduce and m/reductions call the reducing function with no argument to get the seed if not provided, which happens to return undefined in clojurescript if arity 0 is not implemented

1
thanks3 1
alex16:09:23

Thank you! In the above scenario, if I provide [] as the initial value for both m/reductions and m/reduce, I get the following log:

=> :empty? []
=> :val 0
=> :empty? [0]
=> :reduce [0]
The :empty? [] log presumably comes from the initial value of m/reductions. So when a flow is initialized, it's run once (seeded?) before any emitted values come through the flow?

leonoel18:09:45

1. m/reduce is run, which recursively runs all flows in the pipeline. m/reductions transfers the seed to m/eduction which passes it to the transducer. :empty? [] is printed, remove predicate returns true, initialization is done. 2. wait 1s 3. m/observe gets the external event, transfers it to m/reductions which calls a reduction step, :val 0 is printed, 0 is conjed to the accumulated vector and transferred to m/eduction, which passes it to the transducer. :empty? [0] is printed, remove predicate returns false, [0] is transferred to m/reduce which calls its reduction step, :reduce [0] is printed, propagation is done. 4. goto 2

thanks3 1
1