I am back to bug hunting. Reactive live coding is ...
# two-minute-week
t
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.
i
Yay for visualizing execution across time. Crying out for a band-select zoom :)
t
Oh yeah, totally. I keep squinting and increasing my browser zoom, lol. Thing is, I don't care about this tool. I invest the absolute minimum needed to solve the specific tactical problem I have at the time, because my main aim to solve something else. So I have spend close to zero time thinking about it as an actual feature. But I keep coming back to it and adding little tweaks for over a year now, so I recently concluded you can't really do reactive programming without something like this. The bugs are too hard otherwise. It might be Observable brand of reactivity but I felt like this in RxJS as well, reactivity is good until you get out of the toy zone. So now I promoted it to core tooling, and I guess that means I need to think about it being easier to use. I was actually thinking about cells want to display with a search field or something coz the vertical dimension doesn't really mean anything (categorical). Good feedback, thanks.
i
Would be nice if the environment we were working in made the "easy" thing the "right" thing.
t
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.
Copy code
Parent
           /     \
 async task1     async task2
            \   /
           common
j
t
I love that article. Read it a few times! I also think Observable does a good job at transitioning the system between internally consistent snapshots, the schedular and invalidation logic is quite sophisticated. Maybe its impure side effects. I dislike complex type systems though, maybe they are needed here 🤔