Repository navigation
Partial Types #11233
Copy link
Copy link
Closed
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Milestone
Description
This is a proposal for #4889 and a variety of other issues.
Use Cases
Many libraries, notably React, have a method which takes an object and updates corresponding fields on some other object on a per-key basis. It looks like this
function setState<T>(target: T, settings: ??T??) {
// ...
}
let obj = { a: 2, b: "ok", c: [1] };
setState(obj, { a: 4 }); // Desired OK
setState(obj, { a: "nope"}); // Desired error
setState(obj, { b: "OK", c: [] }); // Desired OK
setState(obj, { a: 1, d: 100 }); // Desired error
setState(obj, window); // Desired errorObservations
- The update is rarely recursive (an exception is Mongo's
query); this is a "shallow" operation - It's not desirable to allow "new" properties to appear
- We're not looking for a supertype of some type (due to the non-recursive nature)
Unary partial T operator
A type partial T behaves as follows:
- Its properties are the same as the properties of
T, except that those properties are now optional - A
Stype is only assignable to typepartial TifShas at least one property in common withT- Otherwise regular structural subtype/assignability rules apply
- The type
partial (T | U)is equivalent to(partial T) | (partial U) - The type
partial (T & U)does not expand (this would interact poorly with rule 2) - Any type
Tis always a subtype ofpartial T - The type
partial Tis equivalent toTifTisany,never,null, orundefined
More thoughts to come later, likely
Reacted by normalser, Jan Žák, Kitson Kelly, Dylan Schiemann, Ali Taheri Moghaddar, Carl, David, Jan M., Rick Carlino, Ilja Kroonen and 38 more
Reacted by Michael Konviser, Akim, Maksym, Josh Ghoulberg 👻, Umed Khudoiberdiev, Pavel Birukov , Scott Cheng, Jan Zieliński and tsamsiyu
Metadata
Metadata
Assignees
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Activity
mhegazy commentedon Sep 28, 2016
I thought we are not lumping week type detection in this proposal. so why the change?
RyanCavanaugh commentedon Sep 28, 2016
Weak type detection everywhere would be a breaking change, whereas here we have an easy place to restrict the bad assignment.
DanielRosenwasser commentedon Sep 29, 2016
Do excess property checks still apply though?
RyanCavanaugh commentedon Sep 29, 2016
Of course
tinganho commentedon Sep 29, 2016
This is not the same as covariance/contravariance #1394 ?
kitsonk commentedon Sep 29, 2016
It is one aspect of covariance I believe (in that
subset Tis covariant toT), but it is not a whole system of being able to deal with generic types in a full covariant/contravariant way. Covariance/contravariance implies the relationship of the whole type, versus just optionality of presence of particular properties of the type.I would personally rather take this pragmatic approach to dealing with the usage patterns in JavaScript/TypeScript then wait for a total covariant/contravariant solution. I suspect this would not interfere with an eventual full solution (e.g. support C# type
in Tandout Tgenerics).kitsonk commentedon Sep 29, 2016
On the weak type detection, I assume with something like this:
The only challenge I see with that is that sometimes that might be unavoidable, but I guess that is an edge case.
Also what about something like this:
yortus commentedon Sep 29, 2016
Can you explain the choice of the name 'subset'?
The set of values in
Subsetis a superset of the set of values inT, if I understand this right?Union and intersection types are named to describe how their value sets are constructed from the value sets of their constituent types. So it seems strange that
subset Tdescribes the construction of a value set that is a superset ofT's values.RyanCavanaugh commentedon Sep 29, 2016
I forgot to mention the special case that the empty type is not subject to the "at least one property in common" rule 👍
Hopefully the intuition is at least apparent - that
subset Thas a subset of the properties ofT. Whether we name something according to the set theory operation applied to the properties or to the values is somewhat arbitrary and while consistency is desirable, I'm not sure there's a good name that conveys the intent when seen in the other formulation of the domain, especially since the rule isn't intended to be recursive (which rules out things likesuperIMHO). Open to bikeshedding on this point.Igorbek commentedon Sep 29, 2016
that wouldn't work with generic types. Need to better describe assignability rules, like:
Sis a subtype ofTthensubset Sis a subtype ofsubset TRyanCavanaugh commentedon Oct 3, 2016
Prototype (no generics, no sealedness) is working well
Open questions I have when implementing this:
subset T | Uequivalent to(subset T) | U, orsubset (T | U)?subset (T | U)is exactly(subset T) | (subset U), is this correct?subset (T & U)is exactly(subset T) & (subset U), is this correct?subset? Flow uses$Shapewhich is not intuitive at allkitsonk commentedon Oct 4, 2016
My opinions for what they are worth:
Wouldn't these be optional too. Actually that might come in super handy when trying to deal with creating decorated functions:
I guess though, that raises the question in my head, is a subtype "frozen" at the point it is evaluated and assigned? For example:
These should persist unmodified to the
subsetand essentially be the only thing "required" in the subset.Would it be the same order of precedence as the
typeofkeyword in the type position? Which then only operates on the next type. Sotypeof foo | Baris always(typeof foo) | Barand thereforesubset T | Uwould always be(subset T) | U.We heavily use
type FooBarState = FooState & BarStateand I can't think of a situation where that I would expect(subset FooState) & (subset BarState)wouldn't be equal tosubset (FooState & BarState). I guess the one exception is how you deal with index properties. I am not familiar how they are dealt with in unions and intersections anyways.tinganho commentedon Oct 4, 2016
I'm a little bit concerned that the keyword
subsetmight not be appropriate in this case. My arguments are the same as Troy Gerwien (@yortus), so I won't repeat them . IMOsubsetis not an improvement overpartial, which was the original proposal.Two "type" operator end with
of, such astypeofandinstanceofand not to mention a third proposedkeysof.Since Ryan Cavanaugh (@RyanCavanaugh) mentioned that
superis out of discussion, because it infers recursiveness. I tend to agree, so it rules outsuperset,supersetof,supertypeofetc.What about
shapeof? It's short, concise and consistent.46 remaining items
rozzzly commentedon Nov 7, 2016
Ryan Cavanaugh (@RyanCavanaugh) will object spread stuffs still hit in
2.1or do they have to come together?RyanCavanaugh commentedon Nov 9, 2016
Jason Killian (@jkillian) see #12114 . It slices, it dices, it makes partial types!
AlexGalays commentedon Nov 9, 2016
At last some good news in America!
saschanaz commentedon Nov 9, 2016
Alexandre Galays (@AlexGalays) I hope you are talking about anything related to the partial types.
RyanCavanaugh commentedon Nov 14, 2016
We'll close this with the assumption that #12114 (which is merged now) addresses all these use cases adequately - chime in with a new issue if that turns out not to be the case.
connor4312 commentedon Sep 29, 2017
Hi Ryan Cavanaugh (@RyanCavanaugh), mapped types are fantastic, but it doesn't seem to address the questions above with deep partial types / deep subsets. Mapped types and
Partial<>allow for a shallow subset, but all nested objects must be complete (though there are some dirty solutions). APartialDeepgeneric would be fantastic.The use case is that there is an API I'm building against which allows for object merges of very complex/deep objects.
Igorbek commentedon Sep 29, 2017
Connor Peet (@connor4312)
this works just fine for me.
AlexGalays commentedon Sep 29, 2017
Igor Oleinikov (@Igorbek)
This gives zero control over Arrays for instance. It will iterate over their keys and allow you to access their method as nullable ones (concat, reduce, etc) which makes no sense. Same with a Date, etc.
Igorbek commentedon Sep 30, 2017
ok, understood. My use case did require it. You then might be interested in #6606 with its mapping capabilities.
alvis commentedon Oct 12, 2017
Also the related PR #17961. In fact, I'm facing the same problem. Will be extremely delighted when a solution becomes available.