This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-24
Channels
- # announcements (7)
- # babashka (5)
- # babashka-sci-dev (20)
- # beginners (125)
- # biff (98)
- # catalyst (1)
- # clerk (37)
- # clj-kondo (6)
- # clojure (49)
- # clojure-dev (18)
- # clojure-europe (6)
- # clojure-uk (2)
- # data-science (17)
- # deps-new (20)
- # emacs (11)
- # helix (5)
- # hyperfiddle (34)
- # malli (3)
- # missionary (4)
- # reitit (4)
- # sci (15)
- # solo-full-stack (7)
- # sql (5)
- # testing (2)
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]
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

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?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 conj
ed 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
