Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Mapped types#12114

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ahejlsberg merged 31 commits intomasterfrommappedTypes
Nov 13, 2016
Merged

Mapped types#12114

ahejlsberg merged 31 commits intomasterfrommappedTypes
Nov 13, 2016

Conversation

ahejlsberg
Copy link
Member

@ahejlsbergahejlsberg commentedNov 9, 2016
edited
Loading

This PR introducesMapped Types, a new kind of object type that maps a type representing property names over a property declaration template. In combination withindex types andindexed access types (#11929), mapped types enable a number of interesting and useful type transformations. In particular, mapped types enable more accurate typing of intrinsic functions such asObject.assign andObject.freeze as well as APIs that map or transform shapes of objects.

A mapped type takes one of the forms

{[PinK] :T}{[PinK] ? :T}{readonly[PinK] :T}{readonly[PinK] ? :T}

whereP is an identifier,K is a type that must be assignable tostring, andT is some type that can useP as a type parameter. A mapped type resolves to an object type with a set of properties constructed by introducing a type parameterP and iterating it over the constituent types inK, for each suchP declaring a property or index signature with the type given byT (which possibly referencesP as a type parameter). WhenP is a string literal type, a property with that name is introduced. Otherwise, whenP is typestring, an index signature is introduced.

typeItem={a:string,b:number,c:boolean};typeT1={[Pin"x"|"y"]:number};// { x: number, y: number }typeT2={[Pin"x"|"y"]:P};// { x: "x", y: "y" }typeT3={[Pin"a"|"b"]:Item[P]};// { a: string, b: number }typeT4={[PinkeyofItem]:Date};// { a: Date, b: Date, c: Date }typeT5={[PinkeyofItem]:Item[P]};// { a: string, b: number, c: boolean }typeT6={readonly[PinkeyofItem]:Item[P]};// { readonly a: string, readonly b: number, readonly c: boolean }typeT7={[PinkeyofItem]:Array<Item[P]>};// { a: string[], b: number[], c: boolean[] }

Type relationships involving mapped types are described in#12351. For information on type inference involving mapped types, see#12528 and#12589. For information on preservation of property modifiers with mapped types, see#12563.

The following four mapped types are predefined in lib.d.ts as of#12276:

// Make all properties in T optionaltypePartial<T>={[PinkeyofT]?:T[P];};// Make all properties in T readonlytypeReadonly<T>={readonly[PinkeyofT]:T[P];};// From T pick a set of properties KtypePick<T,KextendskeyofT>={[PinK]:T[P];}// Construct a type with a set of properties K of type TtypeRecord<Kextendsstring,T>={[PinK]:T;}

Some functions that use the above types:

functionassign<T>(obj:T,props:Partial<T>):void;functionfreeze<T>(obj:T):Readonly<T>;functionpick<T,KextendskeyofT>(obj:T, ...keys:K[]):Pick<T,K>;functionmapObject<Kextendsstring,T,U>(obj:Record<K,T>,f:(x:T)=>U):Record<K,U>;

And some code that uses the functions:

interfaceShape{name:string;width:number;height:number;visible:boolean;}functionf1(s1:Shape,s2:Shape){assign(s1,{name:"circle"});assign(s2,{width:10,height:20});}functionf2(shape:Shape){constfrozen=freeze(shape);frozen.name="circle";// Error, name is read-only}functionf3(shape:Shape){constx=pick(shape,"name","visible");// { name: string, visible: boolean }}functionf4(){constrec={foo:"hello",bar:"world",baz:"bye"};constlengths=mapObject(rec,s=>s.length);// { foo: number, bar: number, baz: number }}

ThemapObject example above shows how type inference can be used for mapped types. When inferring from an object typeS to a mapped type{ [P in K]: T },keyof S is inferred forK andS[keyof S] is inferred forT. In other words, a literal union type of all propertynames inS is inferred forK and a union of all propertytypes inS is inferred forT.

Another common pattern:

// A proxy for a given typetypeProxy<T>={get():T;set(value:T):void;}// Proxify all properties in TtypeProxify<T>={[PinkeyofT]:Proxy<T[P]>;}functionproxify<T>(obj:T):Proxify<T>{// Wrap proxies around properties of obj}functionf5(shape:Shape){constp=proxify(shape);letname=p.name.get();p.visible.set(false);}

Related issues include#1295,#2710,#4889,#6613,#10725,#11100,#11233.

tinganho, Igorbek, yortus, normalser, jwbay, svieira, jkillian, alitaheri, s-panferov, HerringtonDarkholme, and 143 more reacted with thumbs up emojiunlanin reacted with thumbs down emojijwbay, svieira, jkillian, alitaheri, ronzeidman, HerringtonDarkholme, aluanhaddad, seansfkelley, kitsonk, tiagoefmoraes, and 61 more reacted with hooray emojiIgorbek, jwbay, gabomgp, alitaheri, HerringtonDarkholme, aluanhaddad, sanex3339, arusakov, EmielM, wclr, and 52 more reacted with heart emoji
# Conflicts:#src/compiler/checker.ts
@jkillian
Copy link

jkillian commentedNov 9, 2016
edited
Loading

This is very cool! It'll be great to be able to have stronger typings for all the sorts of utility-type function this will support.

Just curious, would this allow for typings for a function like_.omit? As far as I can tell, this would require an additional, orthogonal, type operator?

functionomit<T,KextendskeyofT>(obj:T, ...keys:K[]):Pick<T,(keyofT)-K>;
guillaume86, cvle, marcomorain, andrew-w-ross, leonadler, mkusher, ikokostya, vincekovacs, daisylb, and canufeel reacted with thumbs up emoji

@AlexGalays
Copy link

Can it handle non shallow transformations ?

Like

function deepAssign<T>(obj: T, props: DeepPartial<T>): void;

bradenhs reacted with thumbs up emoji

@alitaheri
Copy link

@jkillian Probably after object-rest is in.

@AlexGalays Nope, I checked out the branch. I didn't find a way this can be done.

I tried 2 level partial, this is the best I could get:

typeDeepPartial<T>={[PinkeyofT]?:T[P]|{[SinkeyofT[P]]?:T[P][S]};};typeA={a:number,b:{c:boolean;d:string;}}typeB=DeepPartial<A>;constb:B={b:{c:{}}};

the second part:{[S in keyof T[P]]?: T[P][S]} always ends up being{} and since interfaces cannot have this signature there is no way to recursively define it:

interfaceDeepPartial<T>{[PinkeyofT]?:T[P]|DeepPartial<T[P]>;// nope :(}

Also, There is no way to distinguish between objects and non-objects so everything will have| {} appended to it.

This PR is really really great so far, I love it ❤️ ❤️

I don't think deep partial is possible at the moment. unless it is defined as a builtin modifier (deepPartial) or magic interfaceDeepPartial<T>

GoToLoop and bradenhs reacted with thumbs up emoji

@HerringtonDarkholme
Copy link
Contributor

This might be irrelevant but I really cannot hold the urge to say here:

the most exciting moment when using TypeScript is that after a magic pull request, all the sudden I can express a lot of semantics I couldn't before. That's the very freedom in programming world.

This feature is so awesome. Thanks@ahejlsberg and TypeScript team. Make FrontEnd Great Again!

alitaheri, aluanhaddad, sandord, jonaskello, marcomorain, rd-dev-ukraine, nicotoumetis, IanYates, rictic, wwwillchen, and 5 more reacted with thumbs up emojinormalser, Lenne231, guncha, jwbay, jkillian, svieira, niieani, forabi, alitaheri, mariusschulz, and 9 more reacted with laugh emojimasak and debajyoti-thetaonelab reacted with heart emoji

@ahejlsberg
Copy link
MemberAuthor

@jkillian No active plans to support a type subtraction operator. It's unclear how such an operator would function across all types. That said, it might be possible to have a limited form that only works on primitive (and thus literal) types and unions thereof. That would suffice for the scenarios where you're subtracting keys from akeyof T.

@AlexGalays@alitaheri We currently restrict recursive use of a mapped type, but it's possible to lift that restriction. However, in actual use we'd still need some form of recursion limiter to avoid spiraling down an infinite series of nested mapped type applications. Ideally some sort of type parameter constraint or modifier you could use on a type alias for a mapped type to indicate that applications should short-circuit for certain types. That way you could for example declareDeepPartial<T> to simply yieldT whenT is a primitive type.

@HerringtonDarkholme Thanks! Really appreciate your excitement for our work.

forabi, alitaheri, guncha, jkillian, HerringtonDarkholme, svieira, wclr, fredgalvao, aluanhaddad, davidk-zenefits, and 5 more reacted with thumbs up emojialitaheri, jkillian, tinganho, HerringtonDarkholme, svieira, wclr, fredgalvao, aluanhaddad, GoToLoop, CunningFatalist, and 5 more reacted with heart emoji

@kourge
Copy link

This is highly exciting! And here I thought strict null check was the best thing since sliced bread, and then this came along.

I think "mapped types" is a somewhat vague term, but seeing the syntactic form of this reminds me of dictionary comprehensions in Python, so perhaps "type comprehension" could be a more descriptive term?

evincarofautumn and dheerajbhaskar reacted with thumbs up emoji

@mhegazy
Copy link
Contributor

🚲 🏠 comment. Why not usefor as in

typePartial<T>{[forPinkeyofT]?:T[P]}

this makes the iterative nature of the construct more prominent, and would allow for a future branch option usingif a la Python generator expressions.

niieani, svieira, aluanhaddad, pimterry, tomasdeml, dawidcxx, GoToLoop, and knee-cola reacted with thumbs up emoji

@HerringtonDarkholme
Copy link
Contributor

@mhegazy did you mean something like

typeSubstractType<T,K>{[forPinkeyofTif!(PinkeyofK)]:T[P]}SubstractType<{name:string,age:number},{age:number}>// {name: string}

Type predicate for filtering property?

GoToLoop and niieani reacted with thumbs up emoji

@zpdDG4gta8XKpMCd
Copy link

typescript chose to sacrifice correctness for the sake of "convenience" and ease of learning for attracting as much untutored audience as possible, basically leveling everyone down to the least common denominator

here is the list of hard choices in the making:https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3A%22by%20design%22

Elephant-Vessel reacted with confused emoji

@AlexGalays
Copy link

AlexGalays commentedDec 9, 2016
edited
Loading

@silviogutierrez

Well, in my case I don't want users of my function to extend the base structural type with more properties; I want the full extent of properties declared upfront for typesafety. That rules out returning T & U for me.

Also, be careful with& as it's a bit broken.

If you do

typeA={oops:['1','2','3']}typeB={oops:333}typeC=A&B

You would probably expect an Error, but instead it compiles just fine and the type of oops inside C isstring[] & number which makes no sense.

Partial and the likes seem to have fewer applications that previously thought in their current forms :( It still doesn't solve the React setState problem too :

typeState={veryImportant:number}// This compiles fine with all the mandatory flags (strictNullChecks, etc)// if we use Partial<State>// This is a huge invariant violation and can happen very easily for instance// by setting `veryImportant` to a nullable variable.this.setState({veryImportant:undefined})

Let's start creating some focused bug tickets rather than complaining on this PR :p

@jkillian
Copy link

jkillian commentedDec 13, 2016
edited
Loading

I'm trying to write typings forReact's update helper and am struggling a bit.

Here's what I have so far, trying to get things working for a subset of the library's functionality for now. I'm having a few issues which are noted in the code below:

typeCommand={$set:any;}|{$merge:{}}|{$apply(value:any):any;};typeUpdate<T>={[PinkeyofT]?:Update<T[P]>|Command;};declarefunctionupdate<T>(value:T,updateObj:Update<T>);typeFoo={a:number;b:{b1:number;};}letbar:Foo={a:1,b:{b1:2}};// doesn't work without explicit <Foo>:// The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.//   Type argument candidate 'Foo' is not a valid type argument because it is not a supertype of candidate '{ a: { $set: number; }; }'.//     Types of property 'a' are incompatible//       Type '{ $set: number; }' is not assignable to type 'number'.update<Foo>(bar,{a:{$set:3}})// also doesn't work without <Foo>update<Foo>(bar,{b:{b1:{$set:4}}})// shouldn't work at all, but doesupdate<Foo>(bar,{b:7});

Any ideas for ways to improve things? At the least, I think it may be an improvement overthe current typings, but I wish it could be even better. Having to explicitly specify the type ofT when callingupdate is a big usability issue in my mind

@PyroVortex
Copy link

PyroVortex commentedDec 13, 2016
edited
Loading

You can accomplish it thusly:

typeUpdate<T,KextendskeyofT>={[PinK]:Command|NestedUpdate<T[P]>};typeNestedUpdate<T>={[PinkeyofT]?:Command|NestedUpdate<T[P]>};declarefunctionupdate<T,KextendskeyofT>(value:T,updateObj:Update<T,K>);

The problem is that once you get more than one layer deep, it stops actually validating the type.

SlurpTheo reacted with thumbs up emojijkillian reacted with hooray emoji

@jkillian
Copy link

jkillian commentedDec 13, 2016
edited
Loading

@PyroVortex Thanks! That works much better! I made anupdated example demonstrating the now working cases and the failure case you mentioned in case anyone's interested

@AlexGalays
Copy link

@PyroVortex Seems a bit strange

  • K extends keyof T; why use a second type parameter to have a subset of the keys if the keys are already all optional with? anyway?
  • When Update is called recursively, It is not passed a subset of keys but the full set of keys which is not consistent with the first level

Anyway, as seen in#12769 (comment), you would have to use the nightly build to get close to what you want without some bad side effects currently in 2.1.4.

@jkillian
Copy link

jkillian commentedDec 14, 2016
edited
Loading

Ah, thanks for the reference to#12769@AlexGalays. It looks like Anders' code is quite similar to what I had triedin this comment. Perhaps upgrading to the nightly is the right solution for me here

@wclr
Copy link

wclr commentedApr 4, 2017

Is it possible to get the following, get only those props from an object, that present in another object :

functiontakeProps<Obj>(props:{[KinkeyofObj]?:true},obj:Obj){constnewObj:any={}for(letkeyinprops){newObj[key]=obj[key]}returnnewObjas{[Kinkeyoftypeofprops]:number}}takeProps({a:true,c:true},{a:1,b:2,c:3})// => here I wan't be available only `a` and `c`, but get `a`, `b`, and `c`

@PyroVortex
Copy link

PyroVortex commentedApr 4, 2017
edited
Loading

@whitecolor

functiontakeProps<T,KextendskeyofT>(props:{[PinK]:true},obj:T):{[PinK]:T[P]}

The above declaration has the behavior you are describing.

@wclr
Copy link

wclr commentedApr 4, 2017

@PyroVortex thanks very much)

@wclr
Copy link

wclr commentedApr 5, 2017

There is another question: is it possible to type a function that gets object map with values that are actually functors and returns mapped object applyingmap method of each functor.

interfaceA{map:()=>number}interfaceB{map:()=>string}leta:A={map:()=>1}letb:B={map:()=>'str'}functiongetMapped(sources:{[index:string]:{map:()=>any}}){// implementationconstmapped:{[keyinkeyoftypeofsources]:any}={}for(constkeyinsources){mapped[key]=sources[key].map()}returnmapped}getMapped({a, b}).a// => should be 1getMapped({a, b}).b// => should be 'str'

@spion
Copy link

Yes

interfaceA{map:()=>number}interfaceB{map:()=>string}leta:A={map:()=>1}letb:B={map:()=>'str'}typeMapsTo<T>={map:()=>T}typeMapSources<T>={[KinkeyofT]:MapsTo<T[K]>}functiongetMapped<T>(sources:MapSources<T>):T{constmapped:any={}for(constkeyinsources){mapped[key]=sources[key].map()}returnmapped}getMapped({a, b}).a// => should be 1getMapped({a, b}).b// => should be 'str'
wclr reacted with thumbs up emojiwclr reacted with hooray emoji

@wclr
Copy link

wclr commentedApr 6, 2017

Any advice on how this can be accomplished a "functor" with two mapping methods :

consta={mapR:()=>1,mapT:()=>true}constb={mapR:()=>'str',mapT:()=>false}typeMapsTo<T,R>={mapT:()=>T,mapR:()=>R}typeMapSources<T,R>={[Kinkeyof(T&R)]:MapsTo<T[K],R[K]>}functiongetMapped<T,R>(sources:MapSources<T,R>):{T:T,R:R}{constmapped:any={T:{},R:{}}for(constkeyinsources){mapped.T[key]=sources[key].mapT()mapped.R[key]=sources[key].mapR()}returnmapped}getMapped({ a, b}).R.a// => should be 1getMapped({ a, b}).R.b// => should be 'str'getMapped({ a, b}).T.a// => should be truegetMapped({ a, b}).T.b// => should be false

@spion
Copy link

spion commentedApr 6, 2017
edited
Loading

@whitecolor you might want to try thegitter channel for typescript

Anyway, here is the solution:http://bit.ly/2od3xP5

The reason why the original doesn't work is because all the keys of T & R are not necessarily present in both T and R, so nothing is known about T[K] and R[K]. Moving the & operator to the argument level ensures that the argument is a source of both kinds of "maps"

wclr reacted with thumbs up emojiwclr reacted with hooray emoji

@microsoftmicrosoft locked and limited conversation to collaboratorsJun 19, 2018
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@sandersnsandersnsandersn approved these changes

@mhegazymhegazymhegazy approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

34 participants
@ahejlsberg@jkillian@AlexGalays@alitaheri@HerringtonDarkholme@kourge@mhegazy@Lenne231@stevekane@jods4@BigDataSamuli@niieani@fredgalvao@PyroVortex@danielearwicker@aluanhaddad@zpdDG4gta8XKpMCd@electricessence@TobiaszCudnik@sandersn@silviogutierrez@weswigham@spion@caguthrie@cvle@zamb3zi@benjamingr@arolson101@phiresky@gcnew@yortus@Jessidhia@wclr@msftclas

[8]ページ先頭

©2009-2025 Movatter.jp