My biggest pain points with the Observable runtime have always been mutable state and the implicitness of reactivity in general. Mutable and viewof feel like hacks that completely circumnavigate the reactive parts, relying on an additional event/notification mechanism that is then reintegrated into the runtime through Generators.observe.
Generators work reasonably well on the other hand and, as long as one is familiar with the underlying Javascript mechanics, provide a relatively clear and explicit picture of what is going on.
However, as of now they donāt compose, which makes them limited to cases where only a single generator is sufficient.
It would be extremely nice if there was a cell keyword, which returned a first class representation of said cell. This reified cell could then provide metadata like the name and comments, and contain methods to control and read said cell. The ability to force reevaluation for example is something which I could really use to implement a BibTex style reference system for Scientific Papers, which currently requires a reload.
Most importantly it could provide a method like nextValue() which returns a promise for the next value in the cell.
This would allow for things like:
changingState = {
let i = 0;
while (true) {
yield Promises.delay(0, i++);
}
}
dependentState = {
let i = 0;
while (true) {
yield [i++, await cell changingState.nextValue()];
}
}
Right now, the pair would always be [0, x], as itās not possible to prevent full reevaluation., since mutable doesnāt support generators (it doesnāt provide the correct semantics anyways because we DO want to yield whenever changingState changes, we just donāt want i to be reset).
Being able to explicitly await cells would make a lot of code a lot easier to understand and straightforward to write.
Yeah, this however suffers from its inversion of control and makes code quite messy and hard to understand, especially when showing it to people new to observable.
Edit: Also your changes introduced a subtle bug, which nicely illustrates the point .
Due to the i++ you will ālooseā one increment per full reevaluation, repeating the previously returned one, which is pretty hard to spot.
Can you provide a more āreal worldā example to motivate this use case? Itās not clear to me why knowing the number of times that changingState has changed (since the notebook loaded? since the changingState cell was edited?) is useful.
There are a variety of ways to accomplish things like this, but I think the discussion might be productive with a more concrete example. Thanks!
Also, yes, I donāt recommend using this. Probably the closest translation of the example given would be using mutable, but again, itās a little abstract to say.
As for a concrete example of the generator use case:
Iām writing the GUI for a NLP parser in observable, including a visualisation for the search graph, and would like to keep a d3.simulation object in one cell with the current nodes and their positions yielded with every tick. I then have several visualisations (different canvases) render these nodes whenever they change with different visuals and interactivity. The cell managing the simulation itself is also dependent on another stateful āsearch-spaceā cell which adds new nodes as they are explored.
Iāve experimented with various ways to propagate these changes over the time, and found while(true) ... await ... yield ... to be the most straightforward pattern to explain to ānon-observablersā. Mutable doesnāt really solve this case because it either provides no reactivity at all, or just regular reactivity which has the same āhow to keep the boilerplate stateā problem which can only be solved with this.
As for the BibTex example:
Your example notebook doesnāt work for me (things donāt get removed when I delete them), but I get the idea.
The issue Iāve had when building a Table Of Contents and a Bibliography, is that repeated reevaluation of cells not only introduces and removes elements, but it messes with the ordering (especially non-ideal for TOCs, so Iām currently including X.Y.Z header labels and parsing them).
Cells might get moved around, or they might get reevaluated in a random order.
Being able to inspect the cells directly would either allow to also get info on their ordering (this doesnāt catch inner cell ordering), or to remember which cells partake in the TOC/Bibliography and clearing the TOC/Bibliography-state and forcefully reevaluate all of them whenever one of them reevaluates.
This sounds like you are fighting the representation issue between:
a stream of states Vs. a stream of delta updates
If you underlying state is a set of objects. you can either transmit ADD/DELETE operations (stream of updates) OR, you can transmit the whole set each time (stream of states).
Things are usually simpler in the stream of states world, as the representation is fully encapsulated. There is no outside state you need to track in orderer to decode the updates. Itās robust against missing events (important in distributed environments). But I have noticed many people try to go the stream of updates route for either performance arguments or I think its more intuitive because its nearer the event model we have on UI.
Performance-wise you can make stream of states competitive, because if your state uses a functional immutable representation you can usually make the stream of states very close performance wise. Stream of states is generally easier to compute with downstream as there is not āhidden stateā to track. Coarsely, the stream of states approach is the functional one, and the stream of updates is the imperative one.
If you trying to streamify a dikjstra search for instance, but you want to highlight the most recent expansions, you might transmit the whole expansion history each tick e.g.
If you worry about performance then you can represents complex objects efficiently with Immutable.js leading to log(n) updates, but I personally would not worry about it.
@tomlarkworthy Interesting but a bit besides the point .
The contents of the stream (snapshots vs. deltas), are orthogonal to the mechanics of processing them (with stateful operators).
In my concrete example Iām also streaming the entire snapshot of node positions. The issue is that itās really cumbersome to have āstatefulā cells / data-flow operators (in my case they have a DOM element which needs to be retained), because you either use this (easy to get wrong because control flow is very implicit, and unintuitive for newcomers because it overloads this in a non-standard way), or you have to use mutable which brings in the need for side effects and additional message-passing mechanisms outside of control flow e.g. EventTarget and āinputā events as done for viewof.
However you lose the state management offered by the Runtime and have to ensure that handlers get removed on invalidation. Also, no dependency resolution on import.
Yeahā¦
The version I made doesnāt go through events though, so I donāt think it has the invalidation problem.
The rest is the price you pay for working around the runtimeā¦