Fork me on GitHub
#missionary
<
2023-01-10
>
denik20:01:20

how would one express a flow that wraps a callback based fn?

denik20:01:09

e.g. how to turn this function into a flow that can be sampled infinitely?

(defn cb-fn [cb]
  (cb (rand-int 1000)))

xificurC20:01:33

(m/ap (m/? (m/?> (m/seed (repeat (m/sp (cb (rand-int 1000))))))))
or
(m/ap (loop [] (m/amb (cb (rand-int 1000)) (recur)))
Untested, so hopefully no typos

denik20:01:03

I mean how to invoke this function and get the value passed to cb as the flow value

denik20:01:30

I’m trying to poll an async browser api

xificurC20:01:46

btw if you're struggling with this in photon there's already poll-task in the repo

denik21:01:13

I’ll have a look

denik21:01:10

what about the cb part

denik21:01:05

how to turn an async (cb-based fn) into a flow?

leonoel21:01:58

observe is generally a good fit for callback based apis

denik21:01:20

this is actually passive (promise) based

xificurC21:01:45

I see I misunderstood your question, sorry about that. Can you provide a more concrete example? Which API are we talking about?

denik21:01:46

I just can’t figure out how to plug missionary in as the cb/consumer of the value

denik21:01:18

location api

(defn location
  ([]
   #?(:cljs
      (p/promise [resolve reject]
        (location resolve))))
  ([cb]
   #?(:cljs
      (when-let [gl (j/get js/navigator :geolocation)]
        (j/call gl :getCurrentPosition
                (fn [pos]
                  (let [{:keys [accuracy altitude latitude longitude]}
                        (j/lookup (j/get pos :coords))]
                    (cb
                      {:accuracy  accuracy
                       :altitude  altitude
                       :latitude  latitude
                       :longitude longitude}))))))))

denik21:01:52

this returns a promise on arity 0 or takes a callback on arity 1

denik21:01:22

ideally I’d have a p/def in photon that updates once 1 minute with the users current location

denik21:01:47

e.g.

(p/def user-location (new ...))

xificurC21:01:15

At the call site you'd just (new get-location)

denik21:01:02

it renders but does not update

denik21:01:33

simplified to

(defn cb-fn [cb]
  (cb (rand-int 1000)))

(p/def get-location
  (m/observe (fn [!]
               (cb-fn !)
               ((m/sp (loop []
                        (m/? (m/sleep 10))
                        (m/amb (cb-fn !) (recur)))) {} {}))))


(p/defn App [route]
  (p/server
    (binding [db (p/watch db/conn)]
      (p/server
        #_(dom/div "route: " route)
        ;(Home.)
        (p/client
          (dom/div
            (new get-location)))
        ))))

xificurC21:01:27

change p/def to def

denik21:01:02

now it does not render

xificurC21:01:29

fixing, give me a min

denik22:01:36

this works

(p/def t
  (m/observe
    (fn [!]
      (cb-fn !)
      ((m/sp (loop []
               (m/? (m/sleep 1000))
               (sdom/location !)
               (recur)))
       {} {}))))

xificurC22:01:25

I'm sorry I'm too tired at this point

denik22:01:51

all good, I got it to work somewhat well

denik22:01:55

however:

(p/def user-location-working
  (new
    (m/observe
      (fn [!]
        ;; this works
        (! nil)
        ((m/sp
           (loop []
             (m/? (m/sleep 1000))
             (sdom/location !)
             (recur)))
         {} {})))))

(p/def user-location-breaking
  (new
    (m/observe
      (fn [!]
        ;; this breaks
        (sdom/location !)
        ((m/sp
           (loop []
             (m/? (m/sleep 1000))
             (sdom/location !)
             (recur)))
         {} {})))))

Dustin Getz22:01:26

i think that loop can crash if it is faster than the consumer backpressure

denik22:01:20

right, it would be better to have a flow-like abstraction that lazily gets the location when sampled

denik22:01:35

I just couldn’t figure out how to do that, especially with a callback based api

Dustin Getz22:01:42

ok clearly you understand, we can improve it later i’m afk

👌 2
Dustin Getz22:01:28

it might be complex you can see the photon dom/clock if you are interested i believe it schedules next sample as a side effect of sampling or something like that

denik22:01:53

hmm, I’m not sure it even needs to do that

denik22:01:13

could still use m/sample with dom/<clock to get the values at certain times

denik22:01:25

just need location as a flow

Dustin Getz22:01:42

you’re right i think

xificurC10:01:58

here's an implementation of a task that returns the result of running a callback and a flow wrapper around that task. You'll need to sample it with a clock. (see Leo's answer below)

xificurC11:01:39

it's a discrete flow, to use it in photon you'll need to wrap it e.g. (m/reductions {} nil location>) to provide an initial value

leonoel12:01:59

> it would be better to have a flow-like abstraction that lazily gets the location when sampled This is not possible because sampling is synchronous and the location api is asynchronous. @U09FL65DK’s approach is correct, first build a discrete flow that eagerly polls the api at regular intervals then turn that into a continuous flow with reductions + relieve

denik18:01:46

thank you all

denik18:01:12

@U09FL65DK I tried your impl but cannot get a value to render

denik18:01:03

the following renders a single value that does not update. the other permutations froze up the browser tab

(p/defn MockLoc []
  (p/client
    (let [location (->> location>
                        (m/eduction (take 1))
                        (m/reductions {} nil)
                        #_(throttle 1000)
                        #_(m/latest identity)
                        new)]
      (dom/div
        "value " (pr-str location)))))

denik18:01:42

a sleep in the flow prevents the tab from freezing

denik18:01:44

(def location>
  (m/ap (loop []
          (m/? (m/sleep 100))
          (m/amb (m/? (get-location)) (recur)))))

denik18:01:23

however, can it sleep after returning the first value?

denik19:01:36

okay, got it:

(def location>
  (m/ap (loop []
          (m/amb (m/? (get-location))
                 (do (m/? (m/sleep 2000)) (m/amb))
                 (recur)))))

(p/defn MockLoc []
  (p/client
    (let [location (->> location>
                        (m/reductions {} nil)
                        new)]
      (println location)
      (dom/div
        "value " (pr-str location)))))

👏 2
denik19:01:48

the missionary learning curve is no joke!

Dustin Getz19:01:07

Leo's going to have to write a book 🙂

😁 2
denik19:01:10

well, missionary is aptly named. I’m getting converted!

😄 2
leonoel19:01:49

glad to hear that !

🙏 2