This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-28
Channels
- # beginners (2)
- # calva (8)
- # capetown (1)
- # clojure (28)
- # clojure-europe (6)
- # clojure-norway (82)
- # clojure-sweden (1)
- # clojuredesign-podcast (5)
- # clojurescript (26)
- # core-async (3)
- # cryogen (7)
- # datahike (30)
- # datomic (10)
- # figwheel-main (8)
- # honeysql (8)
- # hyperfiddle (15)
- # jobs-discuss (6)
- # lsp (6)
- # matrix (6)
- # off-topic (12)
- # overtone (1)
- # polylith (6)
- # portal (6)
- # releases (1)
- # shadow-cljs (9)
- # sql (1)
- # xtdb (5)
i was reflecting on the history of javelin recently and concluded something i'd be curious to receive validation on from an outside expert
i know that broadly reactivity can be done either "push" or "pull". push (javelin) is when you propagate values when they're set; "pull" (reagent, matrix?) is when setting a value merely invalidates a cache, and it's looking-at/dereferencing the graph that causes the cascade of requisite computation
each have their tradeoffs, but there's one that i didn't realize was a tradeoff until reflecting. in the pull system, since writing/modifying is merely cache invalidation, "in-transaction" (function calls on same stack) values are consistent with respect to one another. that is, you can modify a reactive value and refer to it on the same stack and not be surprised by its value, since writing does not necessarily trigger reactive effects
i realized that in javelin we had to simulate this with our dosync
construct, which is a delimited pull system of sorts. but in a full pull system you get it for free.
does that overall seem not insane and/or correct?
Perhaps a quibble, but Matrix does have lazy cells, of different varieties: lazy until read once, always lazy, lazy after intially evaluating eagerly. And another quibble: observer aka watch side-effects are dispatched immediately when each property changes, tho a "deferral" mechanism exists (a) so watches can enqueue a different MX state change or (b) so actions can be deferred to an optional client handler that runs after every change to de-dup/reorder/whatever actions recorded for it by watches. Possible quibble, Reagent does IIRC eagerly dispatch view functions when ratoms change. But I get that that is "pull" for everything else. This one I do not grok, and seems important:
you can modify a reactive value and refer to it on the same stack and not be surprised by its value, since writing does not necessarily trigger reactive effects
Does "not be surprised" mean I expect the value to appear modified or not? The follow-up "since writing does not trigger..." makes its sound like I would not see the new value. Anyway, lazy or not, user code in Matrix cannot read an obsolete value, because currency is guaranteed by the read API. I call it JIT consistency. Note that this then applies to watch functions, which may read other properties to execute their side effects: they are guaranteed to see any effect of the original modification.
Fun note: cell X aborts its change processing if it discovers it is already completely changed. How? Because a watch function on some cell Y, dependent on X, fired and asked for X. The second (or third or fourth) time through recursively, X was allowed to finish in peace, and when higher stack change processing sees that, it just stops.
Scroll down https://tilton.medium.com/the-cells-manifesto-b21ed10329f0 to "Data Integrity" for more a few more deets.
As for Javelin's dosync
, MX used to have a macro "with-one-pulse" that did not propagate until the body was executed. I looked at resurrecting that a couple of months ago when I saw an event handler kicking off two cell changes, and it seemed they should be one change, if only for efficiency. But I decided to wait for a good use case.
I think I have muddied the waters sufficiently 🤯 so I will await follow-ups. tl;dr Our mental models do seem to differ.