(no title)
drostie | 7 years ago
This is not to say that data modeling is not important, or that your model won’t have a similar shape as your data model; it will. But that is not the original point.
In MVC as it was originally understood, a model is a list of subscribers. You can add yourself to that list, you can remove yourself from that list, and whenever a “value” changes, if you are on that list, you get a notification that the value has changed. You have a couple of different designs of these, depending on whether you want to send a current-state message as a notification on subscription, or just give everybody read access to the current state. The latter commits you to initializing your models in ways that indicate the absence or staleness of data (someone loads your app, you need to send out a network request to get a new thing) but also allows you to use variable scope to augment how much multiplexing and state tracking you need.
Models vary from data models in having view-relevant data. So for example you switch from the type
const urlList = new Model<UrlRow[]>([])
to the type type Fetching<x> = { state: "init" }
| { state: "loaded", data: x },
| { state: "fetching", staleData: x },
| { state: "fetch error", staleData: null | x }
const urlList = new Model<Fetching<UrlRow[]>>({
state: "init"
})
Notice that these Fetching indicator statuses are a part of the data model for the UI, not the underlying data model that exists at the database level.If this begins to feel a lot like React and Redux, that is because there is a shared lineage there. React originally made its splash as “the V in MVC” but its setState/state system, while it doesn't contain a subscriber list, effectively does something equivalent by insisting on destroying anything that has the old values and then superficially identifying things which appeared to remain the same from moment to moment, with the basic model then being a “subscription tree.”
Redux of course takes this from being a tree to being something more generic by making the store global... I think that models should not be app-global the way that Redux likes (it does not want to solve the problem of multiplexing models, which is understandable but it turns out to be a much simpler problem than you'd think) and that the pattern of reducers is more verbose than one generally needs, but I love the time travel browser features that Redux gives me. Somehow Redux has encouraged abominations like redux-thunk which complect separate concerns into the same dispatch function unnecessarily. But the fundamental workings involve that same basic structure: a subscriber list.
acemarke|7 years ago
Could you clarify why you feel that redux-thunk is an "abomination"? What specific concerns do you have?
I wrote a post a while back that answered several concerns I'd seen expressed about using thunks [0], and my "Redux Fundamentals" workshop slides [1] have a section discussing why middleware like thunks are the right way to handle logic [2].
Also, what do you mean by "multiplexing models"?
[0] https://blog.isquaredsoftware.com/2017/01/idiomatic-redux-th...
[1] https://blog.isquaredsoftware.com/2018/06/redux-fundamentals...
[2] https://blog.isquaredsoftware.com/presentations/workshops/re...
drostie|7 years ago
Multiplexing two models together to my mind just constructs a new model whose values are tuples of the existing models and which subscribes to both of those models in order to notify its own subscribers whenever either side of those tuples change. If you do this, you can have a bunch of local stores and still say "this component's value changes whenever either of those values change." The key is that "normal" models accept a set(value) message to set their value to something, and you might play around with "dict" models which accept insert(key, value), deleteAt(key) messages, but a multiplex model would not be easily able to abstract over all the different possible messages to send upstream and so the easiest approach is just to make multiplexes nonresponsive -- you can't "dispatch" to them, in Redux terms.
redux-thunk is nice in that it helps keep people from making the mistake of sending otherwise-no-op actions to the store which then, inside a reducer as side-effects, do a bunch of async I/O to compute events that eventually make it back to the store. I would broadly agree with that.
My basic beef with redux-thunk is that it's unnecessary and complicates what would otherwise be a type signature that has no reference to I/O, which I regard as a good thing. Developers ought to know that, to quote one of the Austin Powers movies, "you had the mojo all along." It's a sort of talisman that you are using for purely psychological reasons to reassure developers and to coax them to doing updates outside of the reducers, but it's OK because "it's in `dispatch()` so it must be a Redux thing so we'll make it work." But such a talisman is unnecessary.
Like, here's what you've got (in the readme):
Here it is with simpler type signatures: The indirection here wouldn't even be necessary if Redux core just preventatively declared when creating a store that which I mean maybe you do; I don't know.checkurhistory|7 years ago
This doesn't seem to track with a cursory search on the history of MVC. See some of the older papers on this site by the creator of MVC: http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html
From 1979 (http://heim.ifi.uio.no/~trygver/1979/mvc-1/1979-05-MVC.pdf) (also alluded to in the linked article)
> A Model is an active representation of an abstraction in the form of data in a computing system
I don't see any description in terms of subscribers.
drostie|7 years ago
For some references you could find e.g. http://wiki.c2.com/?ModelViewControllerHistory
> The dependency (addDependent:, removeDependent:, etc.) and change broadcast mechanisms (self changed and variations) made their first appearance in support of MVC (and in fact were rarely used outside of MVC). View classes were expected to register themselves as dependents of their models and respond to change messages, either by entirely redisplaying the model or perhaps by doing a more intelligent selective redisplay.
or (PDF Warning) http://www.math.sfedu.ru/smalltalk/gui/mvc.pdf :
> Because only the model can track all changes to its state, the model must have some communication link to the view. To fill this need, a global mechanism in Object is provided to keep track of dependencies such as those between a model and its view. This mechanism uses an IdentityDictionary called DependentFields (a class variable of Object) which simply records all existing dependencies. The keys in this dictionary are all the objects that have registered dependencies; the value associated with each key is a list of the objects which depend upon the key. In addition to this general mechanism, the class Model provides a more efficient mechanism for managing dependents. When you create new classes that are intended to function as active models in an MVC triad, you should make them subclasses of Model. Models in this hierarchy retain their dependents in an instance variable (dependents) which holds either nil, a single dependent object, or an instance of DependentsCollection. Views rely on these dependence mechanisms to notify them of changes in the model. When a new view is given its model, it registers itself as a dependent of that model. When the view is released, it removes itself as a dependent.
Like, I'm not getting this out of nowhere; at one point I inspected the code in the Model object and that's how it works...