4 ms·
"With mutable data structures, it is trivial to guarantee that there is only one version of a certain domain object in memory." That statement refers to the fa
by mweststrate 11y ago
"With mutable data structures, it is trivial to guarantee that there is only one version of a certain domain object in memory."
That statement refers to the fact that you loose (automatic) referential integrity when you start using Immutable data.
Take the following scenario: you have an app with Users and Tasks. Tasks can be assigned to users. When you express this using immutable data you have two problems. The first one is linking tasks to users. If you would link Task1 to Person1 and after that change the name of Person1, you would get a new person, Person1v2. However Task1 would still refer to the old, unmodified Person1, so then you have suddenly two versions of the same person in memory. A fresh and a stale one. In other words, you lost referential integrity.
Now the usual way to fix is, is to not use real references, but to normalize data and store only a key to the person. The effect of this is that you have to always lookup the person related to Task1 again from the state tree. If you have a reference (variable) to that person somewhere, make sure it is short-lived because you won't know if you are still referring to the correct one.
The second problem is that when you have correctly normalized your data and always do fresh lookups, you will still not know _when_ to make these lookups. If you have a TaskView that renders the name of the related Person, and the related person changes, the TaskView will not detect this automatically. The related person is not even a prop of the TaskView component so pushing a new state tree through your app won't repaint the TaskView if you are using PureRenderMixin (which is usually the purpose of using immutable state trees in the first place). For that issue there is a solution as well; you can introduce selectors (or lenses) that query the state tree to detect if the relevant person is changed. So now we already have three new concepts (no refs, normalization, lookups, selectors) to solve a problem that mutable data doesn't have in the first place.
By using mutable data and observing it using MobX these problems are solved (imho) more elegantly: references are never stale because if you would modify Person1, Task1 would still refer to the 'latest' person1, because it is still the same object. Secondly, because it is the same object, fine grained object observers can be established automatically for you, making sure the TaskView gets updated as soon as something relevant changes. This means that you have less concepts to learn and maintain (no copy-on-write, no need to assign a unique key to everything, no data normalization, no lookups, no selectors to make sure your views are always in sync with the state). That saves a significant amount of boring yet error prone work when your application's state model becomes more complex than the average Todo app.
So that is the long story behind that short comment. I hope it clarifies the statement!
- monfera 11y agoInteresting discussion! You make references to database tech so what I describe is known to you but I'd just add some obvious thing in case it isn't for someone: If something is mutable in real life, such as a child's height, then each measurement (sample) is timestamped, and it'll not lose its validity for this timestamp. This is called valid time. Beyond this, the wall time of the entry may be recorded as well, called the transaction time in bitemporal terms. It is possible that the reading was erroneous or entry was fat-fingered, so a new tuple is created with the (now hopefully) correct weight with the same valid date but a new transaction date. So, again, a new, immutable, persistent entry was made. Databases such as DB2 implement features of SQL:2011 such as temporal tables. They allow the storage of such immutable entries and provide the collapsed (temporally resolved) views. Also, PostgreSQL uses the implementation technique called MVCC which purposefully does the thing you seek to avoid: via Multiversion Concurrency Control, preserve the snapshotted relations as they were at the beginning of a transaction, to ensure Isolation of ACID in the face of concurrency. To me it seems that it's perfectly okay and desirable for observables to stream immutable pieces of data, i.e. values, while not engaging in an overly early binding to an optimization strategy that sacrifices the temporal aspect at the earliest moment - especially in a tool that claims to be a version of Functional Reactive Programming in its one-sentence summary. It is possible to have reducers (scan) that collapse a primary, immutable measurement stream into required temporal resolutions. For example, one such resolution may do away with both valid time and transaction time, just emitting changes, and maybe some 'last measured' or 'last updated' time. Some other reductions may yield analytics, for example, to visualize how often certain values change, or how often values are revised due to reading or entry error. Also, even the most elementary reductions such as key=child may carry with them the relevant timestamps. Deeper analytics may apply some applicable smoothing of the data over time, e.g. a child's weight might be smoothed via LOESS or just cubic splines. Also, the velocity of weight gain may be modeled as the differentiation of the weight over time. We're walking into proper continuous time FRP: this differentiation might be performed via some proper numerical technique e.g. using a five point stencil with backward finite difference. I used the child weight as an example, but it's quite similar if one implements game-like user interactions where timing matters a lot, or consistent views over financial data streams on a trading platform. All in all, I don't immediately see how mutability would add utility in this context, but I'm not familiar with MobX constraints which is why I made more general comments which are not new to you but might be interesting to someone else.