I am back to bug hunting. Reactive live coding is great when it works but the bugs can be devilishly difficult. The best methodology I have is plotting all the transitions on a timeseries and zooming RIGHT IN. Its a technique I keep coming back to and it has fixed quite a few subtle issues. I used to think the dependancy graph would be useful but actually a lot of the reactivity bugs occur via hidden event coupling occurring outside the programming model, for example, mouse events, url events, local storage events. They can cause different cells to trigger each other but not through the normal notification mechanisms.
Yeah I do think often "what are the rules of reactivity to avoid these situations?". I think one risky problem class is joining async streams via different paths, it often ends up double triggering the common one, in a non-deterministic way because of the async tasks in the middle, whereas in normal programming you would join with await Promise.all([task1, task2])
. In Rx this is a zip. Observable went for limited stream combine operators, but even in Rx world, if you zip and then accidentally go out of sync, you get a different type of reactivity bug, so zip is not really the correct answer either, coz there is nothing enforcing the streams are publishing at the same rate.
I look at Rust borrow checker and that it does some kind of resource counting in the type system, so that seems kinda like it might be the right path, but its not "easy", either to develop or for people to use. So I dunno. Its an outstanding problem with reactive systems: joining streams in a non-fragile way. When you visualize it at least the problem sometimes becomes apparent, and visualization also catches the unintended coupling that occurs as well in impure systems. So thats my best workable solution for now.
Parent
/ \
async task1 async task2
\ /
common