-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic models with runtime type-checking #271
Comments
It sounds like a good initial approach. The only part I'm not 100% sold is forcing people to add unique names when creating their own types, but I guess they could just be optional and throw in runtime if those types are being used for a generic model... Also I'd make the second parameter to new an options object, just in case it grows with more stuff in the future. e.g. const point = new GenericPoint({ x: 1, y: 2 }, { genericTypes: [types.integer] }) Would you be up to give a try at implementing it? :) |
Great, thank you very much for your feedback! I'd be happy to give it a try. 🙂 |
This would be my suggested API for
What do you think, @xaviergonz? |
looks good to me :) Maybe it would be possible to extract valueType from the argument of I mean, so calling new would inject baseModelGenericTypes itself |
I think this doesn't work in the general case. The mapping from the generic types of the extended class to those of the base class is not necessarily 1:1, so it must be possible to map the types explicitly. For instance, the extended class might only have one generic type while the base class has two, and perhaps the generic type of the extended class is assigned to both generic types of the base class. Or one generic type of the base class is hardcoded and the other is passed through from the extended class. |
lgtm then |
Apologies for the inactivity here. A quick update: I've started some first steps of implementing this feature, but realized it's actually not so straightforward. Conceptually, I've been wondering whether passing generic runtime types in the model definition ... extends ExtendedModel(<T>(valueType: TypeChecker<T>) => ({
baseModel: modelClass<GenericPoint<T>>(GenericPoint),
baseModelGenericTypes: [valueType],
// ...
}))<T> {
// ...
} is (a) the right approach and (b) sufficient because there are at least two other places that I can see now where something similar is needed:
When comparing this approach with TS types, type aliasing like type NumberPoint = GenericPoint<number> isn't possible, but perhaps if it were, e.g. const NumberPoint = modelClass(GenericPoint, { genericTypes: [types.number] }) the API would also become simpler:
Instantiating a generic model could be done in three ways:
|
In continuation of #239, I'd like to discuss an idea for generic models with runtime type-checking without using a class factory. In #242, support for truly generic models was added by means of a generic arrow function (without arguments) that enables returning generic props. I think that adding arguments to this generic arrow function, being the runtime types that correspond to the TS generics, would be the natural extension for having generic runtime type-checked models.
For instance:
Generic model (TS only):
Generic model (TS + runtime type-checking):
Note that
TypeChecker<T>
currently does not exist yet. TypeScript would derive the type of the 2nd argument of the constructor from the arguments of the arrow function and make sure compatible runtime type-checkers are provided in this array.The sketched approach has some implications on snapshots and reconciliation. Model classes with "generic runtime types" are not uniquely/fully defined in contrast to non-generic model classes because here the same model class can be instantiated with different runtime types. This means the runtime types need to become snapshotable and the ones provided upon instantiation need to be stored in the snapshot along with the rest of the model snapshot.
For instance:
could look like this:
Runtime types with arguments like
types.or
could be represented like this:Thus, runtime types like models would need to have globally unique type names and would need to be registered in a global registry, so that the reconciler can load a snapshot and instantiate the types from the snapshot again.
Refinements would require a globally unique name, so they can be stored in the registry, too. At the moment, the name is optional.
I'm curious about some feedback. 🙂
The text was updated successfully, but these errors were encountered: