dunham3 hours ago
I know it is out of scope for this article, but there are variants where the operations are monadic rather than applicative and the shape of the graph can change depending on values. And also variations with state - where history can be taken into account.
Jane street briefly summarizes some options here: https://blog.janestreet.com/breaking-down-frp/
And they have an interesting talk on the trade-offs and how their own system, incremental, evolved: https://blog.janestreet.com/seven-implementations-of-increme...
noelwelsh11 hours ago
Overall, very nice article. A few notes:
* I think the first implementation in JS land was Flapjax, which was around 2008: https://www.flapjax-lang.org/publications/
* The article didn't discuss glitch-freedom, which I think is fairly important.
xkbear896 hours ago
Glitch-freedom is one of those things that does not bite you until it does, and then you spend a day debugging a UI that renders an impossible intermediate state for a single frame. I hit this in a dashboard project where two derived signals depended on the same source, and without batched updates the downstream computation ran with one stale and one fresh value. The result was a brief negative number in a percentage display, which was only visible if you knew to look for it.
The push-pull approach described here actually sidesteps the worst glitches because the dirty-flag propagation is just marking, not computing. But the article glosses over what happens during the pull phase when the dependency graph has diamonds. Topological sorting during pull is the standard fix -- Preact Signals and SolidJS both do this -- but it adds complexity that matters if you are rolling your own.
Flapjax was doing a lot of this right in 2008. It is wild that the JS ecosystem took another 15 years to converge on essentially the same core ideas with better ergonomics.
boxfire4 hours ago
So yeah topological sorting is one element, but that global stack is a data race! You need to test set inclusion AND insert into it in an ordered way. Global mutex is gross. To do so lock-free could maybe be done with a lock free concurrent priority queue with a pair of monatomic generation counters for the priorities processed then next, then some memo of updates so that the conflicting re-update is invalidated by violation the generation constraint. I see no less than 3 CAS, so updates across a highly contentious system get fairly hairy. But still, a naive approach is good enough for the 99% so let there be glitches!
[deleted]an hour agocollapsed
aatd86an hour ago
Can the reactive graph even be updated concurrently if the UI depends on it though? Because the UI is likely to run in its own single thread...
willybrauner10 hours ago
I didn't know about Flapjax, thanks I'll check it out. Glitch-freedom is indeed a gap in this article. I focused on the signal algorithm exclusively without some implementation optimisation like batching updates; there is so much more to cover! Maybe in a next one, Thanks!
mpweiherop9 hours ago
And before there was "reactive", there were (oneway) dataflow constraints.
And a lot of literature on the algorithms.
I wrote a bit about the connection here:
https://blog.metaobject.com/2014/03/the-siren-call-of-kvo-an...
(It starts in a slightly different place, but gets there)
Also about constraints as an architectural connector.
https://dl.acm.org/doi/10.1145/2889443.2889456?cid=813164912...
pygy_9 hours ago
Batching isn't just an optimisation, not having transactional semantics can cause bugs.
mananaysiempre9 hours ago
I wrote a whole screed here about how glitches are evil and Rx is evil for teaching people they’re normal, but then I thought about it a bit more—
The system as described isn’t actually glitchy, is it? It doesn’t eagerly run any user computations, just dirtying, and that is idempotent so the order is irrelevant. It’s also a bit useless because it only allows you to pull out values of your own initiative, not subscribe to them, but that’s fixable by notifying all subscribers after the dirtying is done, which can’t cause glitches (unless the subscribers violate the rules of the game by triggering more signals).
So now I’m confused whether all the fiddly priority-queue needlepoint is actually needed for anything but the ability to avoid recomputation when an intermediate node decides it doesn’t want to change its output despite a change in one of its inputs. I remember the priority queue being one of the biggest performance killers in Sodium, so that can’t be it, right?..
I’m also confused about whether push-pull as TFA understands it has much to do with Conal Elliott’s definition. I don’t think it does? I feel like I need to reread the paper again.
Also also, some mention of weak references would probably be warranted.
mpweiherop6 hours ago
> whether push-pull as TFA understands it has much to do with Conal Elliott’s definition.
Virtually nothing that is getting sold/branded as "FRP" has anything to do with Conal Eliott's definition.
I once gave a long talk about this here in Berlin, but I don't remember if there was a video.
I've also explained it on twitter a bunch of times, including this memorable sequence:
https://x.com/mpweiher/status/1353716926325915648
Kinda like the Marshall McLuhan scene in Annie Hall ("if only real life were like this")
mananaysiempre7 minutes ago
>> whether push-pull as TFA understands it has much to do with Conal Elliott’s definition.
> Virtually nothing that is getting sold/branded as "FRP" has anything to do with Conal Eliott's definition.
True but not what I meant. The article implicitly (and, in the links at the end, explicitly) refers to his 2009 paper “Push-pull functional reactive programming”, which describes a semantic model together with an specific implementation strategy.
So I was wondering if TFA’s “push-pull” has anything to do with Elliott 2009’s “push-pull”. I don’t think so, because I remember the latter doing wholly push-based recomputation of discrete reactive entities (Events and Reactives) and pull-based only for continuous entities that require eventual sampling (Behaviors).
With that said, I find it difficult to squeeze an actual algorithm out of Elliott’s high-level, semantics-oriented discussion, and usually realize that I misunderstood or misremembered something whenever I reread that paper (every few years). So if the author went all the way to reference this specific work out of all the FRP literature, I’m willing to believe that they are implying some sort of link that I’m not seeing. I would just like to know where it is.
HumanOstrich10 hours ago
After wondering what the heck glitch-freedom is and learning about it, I agree with you. It seems like it deserves at least a brief explanation in an article about how signals work.
I've gone with the universal `alien-signals` package for my project (which doesn't use a frontend framework that includes signals). They show benchmarks of being by far the fastest and have strict limits on code complexity. Those limits are also supposed to avoid glitches by design, and now at least some of that is tested[1].
[deleted]an hour agocollapsed
eviks9 hours ago
The visual examples are rather misleading:
The "2 * x" is rather - why would the reaction from a change in X display many gradual increments of 1 instead of showing the final value once? And then why does Z =Y+1 instead of +1 to Y repeats all the steps again from X? That's not how real signal frameworks work, and also not how you'd imagine they should work
Then the next cascading example: ok, if Signal is a button, not the underlying mechanism behind it, then "computed 1" is also a signal, why isn't it called that? (though intuitively you'd think the moving dots are signals, not buttons)
fabianholzer12 hours ago
I do not want to distract from the content of the article, which is highly relevant for folks who built UIs with frameworks that are conceptually based on signals, but the way that the reading experience is designed really great, in particular the guided reading flow through the instructive code path is something that I rarely have seen done at all, and this even works pretty well on mobile. It's a delightful reminder on how a dynamic medium can be more than the simulation of print on screens.
willybrauner10 hours ago
I searched for the "right way" to explain this algorithm, and ultimately parsing the code was the clearest for me. This isn't always true; sometimes a graph or an interactive module has more impact; it really depends on the topic. Thanks for the feedback.
qprofyeh6 hours ago
Kudos! Minor detail: the code could use a border or bg color different than the text, so that they’re more distinct.
ByteMe957 hours ago
Relevant project for reactive stream programming that gets overlooked https://github.com/Point72/csp
mockingloris9 hours ago
Beautiful presentation... @willybrauner, I would like to read your spin on a follow-up piece on `glitch-freedom`. But in all honesty, this journal entry/post is a work of art; a testament to your journey as a technologist!.
Cheers
willybrauner9 hours ago
Writing technical articles without adding a graphic and playful touch would indeed interest me less. I find that it's the intersection of these two worlds that makes reading enjoyable. Thanks for your kind words!
cloogshicer11 hours ago
What an amazing article. I really like the presentation of text scrolling together with the code. Wonder how this is done under the hood.
willybrauner11 hours ago
Thanks a lot! The left column containing the text is sticky, The right column is absolute and translate on Y and depend on an intersection observer triggered by each scroll section from the left.
cloogshicer8 hours ago
Super cool, thanks for explaining! Is the code available somewhere?
willybrauner7 hours ago
No, unfortunately, my website is not open source. I may consider externalizing the blog in the future, while keeping all the internal article modules.
tubs9 hours ago
Why create an array each time it iterates a Set rather than just iterating the Set?
willybrauner9 hours ago
I create a new copy of the Set because the functions called during the loop might modify it by removing elements via cleanup functions. Without a copy, iterating directly on the Set could cause unpredictable behavior.
willybrauner9 hours ago
But as the article points out, this is a naive implementation. It could be discussed and greatly improved.
[deleted]an hour agocollapsed
cmacleod411 hours ago
Nice presentation, looks like the same thing I implemented in Tcl here: https://wiki.tcl-lang.org/page/ReacTcl :-)
rienbdj11 hours ago
Sodium (and the book that goes with it) is a great resource too https://github.com/SodiumFRP/sodium
danieltanfh9512 hours ago
How is error handling expected to happen here?
tcfhgj12 hours ago
Result<T> should work
ramon15611 hours ago
I love this pattern, but I always felt like squeezing idiom B into idiom A when doing this in e.g. TS. I never tried effect-ts, so maybe I'm missing out. Anyone have experience in this?
nothinkjustai11 hours ago
try-catch
Tade010 hours ago
Now that Angular is adopting signals, building in it has become a much better experience.
Mind you, the framework still has a hostile learning curve, but for those who already made that investment, it's a boon.
esafak6 hours ago
Also in https://svelte.dev/blog/runes
Morpheus_Matrix11 hours ago
[dead]