Fork me on GitHub
#missionary
<
2023-08-28
>
timur18:08:14

Hi guys. I am learning Missionary + Electric, and I've decided to make a tutorial with the things I've learned so far: https://nextjournal.com/N-litened/starting-with-missionary-for-dummies — starting slow, just wanted to share. Please let me know if there are any factual mistakes, want it to be easy to grasp but not technically incorrect.

gratitude 2
👍 4
❤️ 4
leonoel20:08:18

For the user perspective, task/flow cancellation is cooperative like java thread interruption. It is correct that you can't interrupt a thread running a heavy computation, but missionary doesn't magically solve this problem (and cannot solve it in the general case). Otherwise I found the document accurate and enjoyable, looking forward to see your progress !

❤️ 4
Adrian Smith15:08:18

I'm doing something similar, documenting to learn I'm stuck on m/ap so please ping me if you get that documented So far I have: https://github.com/slifin/documentation/blob/main/Learning%20Missionary.md

❤️ 2
timur16:08:00

@UCJCPTW8J, will do! I hope to finish the full guide this week 🙏

timur19:08:22

@U0ETXRFEW @U3X7174KS, I've finished the Tasks section, all task-related functions/macros covered. I'd be happy if you could let me know if your eyes catch anything unclear 🙏 I try not to do a lot of editing for English mistakes — it's not my native language, so I risk bikeshedding myself to a crawl if I start doing it before the complete draft is ready. However, if code examples are ambiguous, or some important use cases are not explained — I'd love to fix those things, as Flows section will be relying heavily on tasks.

pez19:08:07

Your English looks impeccable to me. Not a native speaker either, but I find the prose beautiful and easy to read. I did suggest a more playful title over at X, but it’s fine as it is too. 😃

2
pez19:08:31

A use case that I am specifically interested in stems from a thing I want to do in Electric. I’m sampling wheel events and reducing the deltaY with +. I’m not sure what the end result is called, but I think it might be a flow/signal. It looks like so:

#?(:cljs (defn pinch-state> [pinchable]
           (m/observe
            (fn [!]
              (let [sample (fn [e] (! (when (.-ctrlKey e)
                                        (.preventDefault e)
                                        (.-deltaY e))))]
                (.addEventListener pinchable "wheel" sample #js {"passive" false})
                #(.removeEventListener pinchable "wheel" sample))))))

#?(:cljs (defn pinch-state< [pinchable]
           (->> (pinch-state> pinchable)
                (e/throttle 16) ; RAF interval
                (m/reductions {} 0)
                (m/relieve +)
                (m/latest identity))))
It’s pinch-state< there. Now I want to “derive” (or whatever the term should be) another flow from this one. zoom-state<, which starts at (has an identity of?) 1.0 and then basically just accumulates/adds up the pinch state deltas with a factor. Something like new-zoom-state += 0.025 * pinch-delta (pardon my bad taste in pseudo code). I’m doing it inside a component now, swapping on an atom, and it works, but I’d like to decouple it. Long story, but basically I just would like the tutorial to cover how you derive flows from other flows. (If that’s even a thing.)

timur20:08:25

Yeah, definitely the title should be changed, but I keep it until the end as the ultimate bikeshedder 🙂

timur20:08:29

@U0ETXRFEW (m/ap) and (m/cp) are the tools for deriving flows from flows. Isn't it what you want?

timur20:08:12

If I understand you correctly, you want a continuous flow of zoom-state that you want to accumulate as the sum of all deltas so far. Then in (m/cp) block you should iterate over all new values of discrete flow of pinch-delta via (m/<?) in a loop, (m/amb the-current-result) and (recur) the loop.

timur20:08:29

@U0ETXRFEW, in this case it could be something like this:

(let [pinch-deltas-flow (...)]
	 (m/cp
	   (loop [current-zoom-value 0]
	     (let [new-pinch-delta (m/?< pinch-deltas-flow)
	           new-zoom-value (+ current-zoom-value (* 0.25 new-pinch-delta))]
	       (m/amb
	       	 ;; publish the next zoom-value as a result of our flow
	         new-zoom-value
	         ;; and continue the flow with remaining values
	         (recur new-zoom-value))))))
Though, I suspect there's a bug here when pinch-deltas-flow feeds too fast. Or you could derive a flow of zoom-states as m/reductions from pinch-deltas

Dustin Getz00:08:14

for the record m/cp (continuous flows) has simpler and more intuitive semantics than m/ap (discrete flows), so learn m/cp first imo. Even more so if you are learning missionary as it relates to Electric

xificurC07:08:38

> Now I want to “derive” (or whatever the term should be) another flow from this one. zoom-state<, which starts at (has an identity of?) 1.0 and then basically just accumulates/adds up the pinch state deltas with a factor. Something like new-zoom-state += 0.025 * pinch-delta This doesn't type check. • discrete flows handle discrete events (mouse click, typing, ...). Generally you need to handle every event (can't skip them) • continuous flows are more like timeless values (mouse coordinates, zoom level). You can sample them at any time and it's OK to miss a state. Deltas are discrete, ergo you build pinch-state< to turn an event stream into a continuous value (current zoom %). If you want to run some math on top of that value you can do so with m/latest, e.g. (m/latest #(* 1.25 (inc %)) (pinch-state< my-pinchable))

xificurC08:08:56

@UD9133UCS I think of m/ap and m/cp as the most powerful and therefore last on the list. Just like you don't reduce when filter is enough. E.g. a lot of transformations can be done with m/eduction on discrete flows and m/latest on continuous. The https://github.com/leonoel/missionary/wiki/Cheatsheet can also help

🙏 4
pez08:08:04

Thanks for all this input all of ya! Let’s see if I understand enough to transform it into code that works in my experiment. I’ll get back with my results and it can be input to what should go into that guide (which was my ultimate goal with mentioning my use case after all 😃 ).

🙏 2
timur07:08:06

@U0ETXRFEW @U3X7174KS, I've published new sections on how flows work inside, and what important classifications one needs to keep in mind (in my opinion) when working with flows: https://nextjournal.com/N-litened/starting-with-missionary-for-dummies

👍 2
Adrian Smith13:08:25

@UD9133UCS Thank you the new sections are really illuminating, the bit on the structure of flows is nearly all new to me 🙂

🙏 2
Dustin Getz13:08:57

backpressure, LOL

🙂 2
Dustin Getz13:08:51

the section contains some errors, i'll comment another time. m/relieve is the thing to search for in the #CL85MBPEF and #C7Q9GSHFV channels

🙏 2
timur14:08:40

@U09K620SG, yes, please. I have not yet got 100% facts correct myself, I am learning as I go, experimenting in REPL for every new function/macro I write about. I'd really appreciate if you could point out my mistakes in understanding, so that I can fix them

Dustin Getz14:08:20

understood! we appreciate your contribution, deeply

❤️ 2
timur15:08:45

@U09K620SG the least I can do to thank for the gift of Missionary and Electric Clojure 🙏

gratitude 2
leonoel21:08:31

@UD9133UCSmake-a-flow-from-a-single-value is not a valid flow, because it is illegal to notify twice before transfer. Instead, call (no-more-values!) in deref, just before returning the-single-value. • headful : I plan to use the term initialized in the future. • backpressure : all flows propagate backpressure. What is not backpressured is the callback provided by m/observe. m/observe should only be used by fast consumers like m/relieve to avoid this problem. • finite / infinite : this is not specific to flows. A task can be infinite (e.g. m/never). A database process is infinite, a web server is infinite, a UI is infinite. Rich Hickey would call that a "situated program". All effects (tasks or flows) must eventually terminate, some of them are able to terminate spontaneously but all of them must terminate on cancellation. If cancellation happens before spontaneous termination, the result should be a failure (with missionary.Cancelled, by convention).

timur08:09:19

@U053XQP4S thank you for your reply! > it is illegal to notify twice before transfer Got it, will fix. > headful : I plan to use the term initialized in the future. Thanks, uninitialized/initialized looks good, will change the terminology. > all flows propagate backpressure. What is not backpressured is the callback provided by m/observe. The way I see it in my head, m/observe is the only flow-generating function which creates a non-backpressured flow. As if yes, the callback is not backpressured, which makes the m/observe return a non-backpressured flow as a result, and backpressure has to be relieved in front of it. Is it okay to say that way? Or maybe the terminology should be different to describe what kind of special flow m/observe generates? > spontaneous termination I will put the term in the guide, too. > finite / infinite : this is not specific to flows Yep, I understand that it usually should not matter whether a flow spontaneously terminates or not. Though I think it is an important gotcha to keep in mind for beginners — like, for example, the fact that Clojure's sequences could be finite or infinite, so printlning a random data object might blow up your REPL if you don't think about a possibility of having an infinite sequence somewhere. I've personally stumbled upon blown up REPL too many times with both Clojure's infinite sequences and Missionary's flows, so I want to highlight that it's important to keep potential infinity in mind when debugging.

timur10:09:55

Btw, maybe more correct term would be 'unbackpressurable' rather than 'non-backpressured'?

timur12:09:43

@U0ETXRFEW @U3X7174KS @UCJCPTW8J, I've fixed some of the things that Léo mentioned, and started the Working with flows section: none, reduce, zip, and visually explained the mechanics of amb in ap: https://nextjournal.com/N-litened/starting-with-missionary-for-dummies

🙏 2
👍 2
Reily Siegel14:09:41

> A function seed makes a flow of values from a collection (including lazy sequences) I think you have to be careful here in the case where computing the element of a lazy sequence blocks, eg line-seq.

🙏 2
timur15:09:57

@U6NJBB596, thanks, I agree, a flow should not block in value retrieval — will mention that next to seed in the guide in the next update.

pez11:09:58

@UD9133UCS now finally getting some time to read the new (flow) stuff. I started by rereading the article, and haven’t reached the new stuff yet. 😃 Anyway, I notice that before diving in to tasks, I think I would like a section quickly introducing Tasks and Flows and how they relate to each other.

2
pez12:09:14

A fun experiment I did with the request-user-info example was to use the same sleep value for the “request” as for the wait before cancel. Like 500 ms. Then spam the evaluation command. Got output like this:

clj꞉walkthrough.task-flows꞉> 
Got exception: java.lang.InterruptedException
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false
clj꞉walkthrough.task-flows꞉> 
Got exception: java.lang.InterruptedException
false
clj꞉walkthrough.task-flows꞉> 
Got exception: java.lang.InterruptedException
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false
clj꞉walkthrough.task-flows꞉> 
User 4567: {:user-id 4567, :user-name Timur}
false

👍 2
timur17:09:23

@U0ETXRFEW, thanks, will check it out tomorrow!

pez19:09:56

Am I supposed to be able to evaluate things in the Nextjournal page? I can’t. I am using a Clojure file to where I copy code from the tutorial. Maybe that’s how it’s supposed to work, but it’s a bit confusing.

timur06:09:26

@U0ETXRFEW, I think my document is read-only when published, but I think there should be some button to “fork” it, and edit your own copy :thinking_face: — haven’t looked at it yet. NextJournal’s Clojure experience is quite a bit buggy though (probably a rant for #C01GH5EN7JA and #C035GRLJEP8), so I think you are not missing out much if you just copy-and-paste the snippets to your repl and play there.

pez16:09:42

It works great in my own Clojure file. Wonderful examples! I have a lot of fun playing with them.

❤️ 2
Adrian Smith10:09:05

Hey the Nextjournal link doesn't appear to work any more Is the documentation stored elsewhere?

Dustin Getz17:10:04

@UD9133UCS I also was looking for this today as we are working on missionary docs in earnest this week and the link is down, can you restore it please?

J20:08:08

Missionary + Rama

🔥 18
Dustin Getz20:08:28

post the repo!

Dustin Getz20:08:55

i compressed the video and attached to the gist

Dustin Getz20:08:01

can i post on r/clojure ?

J20:08:45

Of course! You can do it on twitter (x ^^) too

🙂 4
Dustin Getz20:08:58

Do you have a twitter handle to tag?

Dustin Getz20:08:30

why did you use missionary to println, instead of Rama's (.each Ops/PRINTLN)

J20:08:19

I use each on the ETL (to print depot log) and I wanted to let missionary handle the other println.