-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Subset types for easier updating of immutable structures #10803
Comments
I believe this is the same as what's requested in #7004 ? |
Nah, they're not the same. Although I want to do something ultimately similar, there's some details that are not the same. In #7004 the issuer wants to massively change how protected and private properties work w.r.t. type-checking. I've looked at #4889 and #6613, but they both involve supertypes that flat out don't work in this situation. I also narrowed the scope a lot. |
I think the contravariance proposal I suggested with #4889 would solve this (with variadic types though, if the merge has to be variadic; without that it would be simpler): var merge: <Into, ...[From, ...Froms] super ...Into> => Into =
(into, from, ...froms) =>
from
? merge(/* merge operation for `into` and `from` here */, froms)
: into;
interface MyObject {
property1: number;
property2: number;
}
obj: MyObject = ...;
obj = merge(obj, { property1: 1 }, { property2: 2 }); Since both |
We've tried applying generic constraints based on subtyping before. Unfortunately, that's totally broken because of optional properties. Consider Because the original property is optional, and the literal is deduced as required, the supertyping relationship is broken. Simply swapping the constraint around doesn't resolve this problem. |
Hmm, okay, as far as I understand what you're saying it is indeed problematic in this scenario. I kind of assumed a required property would be covariant to an optional one (since it's a stronger constraint) and optional one contravariant to a required one (since it's a weaker constraint), because intuitively you can call a function with parameter of type Is there any concrete reason optionalness is invariant? |
The problem is that the literal object's properties are always deduced as required. So in this case, it's the "base" that has a required property and the "derived" has an optional one, which is an invariant weakening. Avoiding this kind of issue is exactly why I wanted to get straight to the point and just set all the properties to optional, as then there is no need to consider wider subtyping issues or constraints. |
The core use case for this is updating immutable objects. Currently, this can't really be typed properly in TypeScript. Consider
interface MyObject {
property1: number;
property2: number;
}
obj: MyObject = ...;
obj = merge(obj, { property1: 1 });
merge is currently untypable in Typescript. If you require MyObject directly, you will end up in pain as you need to specify all the required properties, even though they're not actually required. If you can take any object, you've lost all type checking. If you use a generic parameter and require that the changes derive from T, you will get an error because the properties can't be optional, even though in this case we simply don't care.
So far the thing I have done is make the properties on MyObject be all optional, but this is really a poor solution as they may well actually be required, and you will end up in pain when you need to use them as such (such as assigning to a compatible interface but with required properties).
To fix this I would like to suggest an "optional" type modifier. The result is a type identical to the original, but all properties are optional. Ideally, this would not behave quite like the empty interface, and would be fully strict about not permitting extra properties.
For syntax I think that a simple ?, e.g. MyObject?, would be sufficient and unambiguous. There should be no emission or compatibility involved here. In terms of signature completion, etc, the "optional" type should be considered more-or-less identical to the original, so this should not require big changes for tooling.
The main use case here does not require support for subtyping, supertyping, or assignability relationships - the compiler can reject all of these. T? is only itself and nothing else. It would be nice if more could be supported but not necessary at all for this feature to be useful. In generic cases, a T? is about as far as it goes. There's also no need for the compiler to support any kind of T - T? compatibility, except that a T is obviously a valid T?. Otherwise these can be treated as two completely distinct types.
I think that this is possibly related to the Object.assign stuff, but my main intention here is that this is a very restricted feature that only needs to do a very small thing, so it should be much less problematic. Also, this is a type-level feature, not a runtime feature/expression, and you can't add extra properties with it.
The text was updated successfully, but these errors were encountered: