Oh, that's me! Feel free to ask me any questions. There's some great discussion over on lobste.rs ( https://lobste.rs/s/2zk3oe/pushing_pulling_three_reactivity ), but I particularly recommend this link that someone posted there to a post that covers much of the same topic but in a lot more detail with reference to various existing libraries and tools and how they do things: https://lord.io/spreadsheets/
Pushing and Pulling: Three reactivity algorithms
11–20 of 22 posts
Re: Pushing and Pulling: Three reactivity algorithms
#12Something I rarely see addressed in articles about reactivity systems is how the desired syntax/developer experience affects the algorithms. For example, I think all the algorithms discussed in this article require knowing the graph topology up front. Even dynamic dependencies need to be known ahead of time, unless I'm misreading. However, take Vue's reactivity for example. Here's a simple input and output: const inp…
Svelte 5 does runtime dependency wiring afaik.
But after working with Svelte 5 daily for a few months, I don't think I like the implicitness. For one, reactivity doesn't show up in type signatures. So I have to remember whats reactive and what's not. Another is that making plain values (string, numbers, etc) reactive is really a trap. The reactivity doesn't cross file boundaries so you have to do weird things like exporting a getter / setter instead of the variable.
Re: Pushing and Pulling: Three reactivity algorithms
#13Re: Pushing and Pulling: Three reactivity algorithms
#14Earlier quoted context omitted.
Svelte 5 does runtime dependency wiring afaik.
From what I understand, Svelte 4 calculated dependencies at compile time. Whereas Svelte 5 does it automatically at runtime based on which values are accessed in tracking contexts (effect / computed value callbacks). This means objects marked as reactive have to be wrapped in proxies to intercept gets / sets. And plain values have to be transformed to objects at compile time. The deps are also recalculated after ever…
Re: Pushing and Pulling: Three reactivity algorithms
#15Something I rarely see addressed in articles about reactivity systems is how the desired syntax/developer experience affects the algorithms. For example, I think all the algorithms discussed in this article require knowing the graph topology up front. Even dynamic dependencies need to be known ahead of time, unless I'm misreading. However, take Vue's reactivity for example. Here's a simple input and output: const inp…
With the variations on the push algorithm, you do kind of need to know the graph topology ahead of time, at least to be able to traverse it efficiently and correctly (this is the topological sorting thing). But for pull (and therefore for push-pull), the dependencies can be completely dynamic - in a programming language, you can call `eval` or something, or in a spreadsheet you could use `indirect` to generate cell references dynamically. For push-pull specifically, when you evaluate a node you would generally delete all of its upstream connections (i.e. for each cell it depends on currently, remove that dependency) and then rebuild that node's connections to the graph while evaluating it.
Signals libraries are exactly where I found this concept, and they work basically like you describe. I think this is a big part of what makes signals work so well in comparison to, say, RxJS - they're basically the same concept (here's a new reactive primitive, let's model our data in terms of this primitive and then plug that into the UI, so we can separate business logic from rendering logic more cleanly), but the behaviour of a signal is often easier to understand because it's not built from different combinators but just described in "ordinary" code. In effect, if observables are monads that need to be wired together with the correct combinators, then signals are do-notation.
Re: Pushing and Pulling: Three reactivity algorithms
#16Earlier quoted context omitted.
Svelte 5 does runtime dependency wiring afaik.
From what I understand, Svelte 4 calculated dependencies at compile time. Whereas Svelte 5 does it automatically at runtime based on which values are accessed in tracking contexts (effect / computed value callbacks). This means objects marked as reactive have to be wrapped in proxies to intercept gets / sets. And plain values have to be transformed to objects at compile time. The deps are also recalculated after ever…
I agree that not seeing reactivity in the type system can be irritating. In theory, you can wrap reactive elements in `Computed` objects (Angular's signals have this, I believe) so you can follow them a bit better, but the problem is that you can still accidentally end up with implicitly reactive values, so it only works as a kind of opt-in "here be reactivity" signal, and you can't guarantee that just because you can't see a `Computed`, that reactivity has been ruled out.
That said, I find I eventually built up a good intuition for where reactivity would be, usually with the logic that functions were reactive and single values weren't, kind of like thunks in other contexts. For me, at least, it feels much simpler to have this implicit tracking, because then I don't need to define dependencies explicitly, but I can generally see them in my code.
Re: Pushing and Pulling: Three reactivity algorithms
#17Something I rarely see addressed in articles about reactivity systems is how the desired syntax/developer experience affects the algorithms. For example, I think all the algorithms discussed in this article require knowing the graph topology up front. Even dynamic dependencies need to be known ahead of time, unless I'm misreading. However, take Vue's reactivity for example. Here's a simple input and output: const inp…
Yeah, the UX/DX of the turning these algorithms into something usable is really interesting, and something I didn't get to talk much about. With the variations on the push algorithm, you do kind of need to know the graph topology ahead of time, at least to be able to traverse it efficiently and correctly (this is the topological sorting thing). But for pull (and therefore for push-pull), the dependencies can be compl…
I've been working in the context of reactivity on the backend where you're often loading up the calculations "from scratch" in a request.
I agree with your monad analogy! We looked into using something like Rx in the name of not reinventing the wheel. If you build out your calculation graph in a DSL like that, then you can do more analysis of the graph structure. But as you said in the article, it can get complicated. And Rx and co are oriented towards "push" flows where you always need to process every input event. In our context, you don't necessarily care about every input if the user makes a bunch of changes at once; it's very acceptable to e.g. debounce the end result.
Re: Pushing and Pulling: Three reactivity algorithms
#18Oh, that's me! Feel free to ask me any questions. There's some great discussion over on lobste.rs ( https://lobste.rs/s/2zk3oe/pushing_pulling_three_reactivity ), but I particularly recommend this link that someone posted there to a post that covers much of the same topic but in a lot more detail with reference to various existing libraries and tools and how they do things: https://lord.io/spreadsheets/
Re: Pushing and Pulling: Three reactivity algorithms
#19Earlier quoted context omitted.
Yeah, the UX/DX of the turning these algorithms into something usable is really interesting, and something I didn't get to talk much about. With the variations on the push algorithm, you do kind of need to know the graph topology ahead of time, at least to be able to traverse it efficiently and correctly (this is the topological sorting thing). But for pull (and therefore for push-pull), the dependencies can be compl…
With your push-pull algorithm, were you considering that the graph had already been built up, e.g. by an earlier pull phase? And the push-pull bit is just for subsequent updates? If so, then I think I'm following :). I've been working in the context of reactivity on the backend where you're often loading up the calculations "from scratch" in a request. I agree with your monad analogy! We looked into using something l…
Re: Pushing and Pulling: Three reactivity algorithms
#20Something I rarely see addressed in articles about reactivity systems is how the desired syntax/developer experience affects the algorithms. For example, I think all the algorithms discussed in this article require knowing the graph topology up front. Even dynamic dependencies need to be known ahead of time, unless I'm misreading. However, take Vue's reactivity for example. Here's a simple input and output: const inp…
Yeah, the UX/DX of the turning these algorithms into something usable is really interesting, and something I didn't get to talk much about. With the variations on the push algorithm, you do kind of need to know the graph topology ahead of time, at least to be able to traverse it efficiently and correctly (this is the topological sorting thing). But for pull (and therefore for push-pull), the dependencies can be compl…