You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat request: allow preserving keys (+ types by key) to map over objects #12393
I'm hoping to be able to properly type functions like Ramda's R.map (for objects) or Lodash's _.mapValues -- in such a way as to acknowledge that mapped over values (which may have been of different types) may give results of different types, dependent on both their respective input type as well as on the mapper function.
This is an issue with the declaration files for ramda as they are specified in the linked repo. The new Mapped Types feature #12114 allows this pattern to be expressed very elegantly.
If you'd forgive a follow-up question (which I can take to SO if deemed inappropriate here), I'm trying to figure out how to generalize the mapObj example of aluanhaddad's Mapped Types link:
... to enable types separated by key, while in this example we seem to have a single T/U for the whole (multi-key) function call.
So I get that this kind of separation could be achieved along the lines of example type T5 = { [P in keyof Item]: Item[P] };, but what's getting me stumped here is how to combine this with the generic function f: (x: T) => U. As in, I suppose U and T would at that point no longer have a single instance, but rather one per key. Would that still be possible?
Thanks! That'll do for now then. :D Mohamed Hegazy (@mhegazy), if this could still be considered an open feature request, would you perhaps reconsider the question tag?
Mohamed Hegazy (@mhegazy), if this could still be considered an open feature request, would you perhaps reconsider the question tag?
Mapped types should enable these scenarios. The change needed is to update the declaration files. if there are other ones that are not covered please provide more information about why mapped types is not sufficient.
Mohamed Hegazy (@mhegazy): I apologize, I'll admit my original example ended up not covering the breadth of my intended result. I've tried to update it accordingly.
The essence here is covered by the
U and T would no longer have a single instance, but rather one per key
.. which HerringtonDarkHolme stated is not presently possible.
Note that this becomes relevant in my (updated) example as different keys end up with different value types, dependent on both the transforming function (e.g. arrayify but could be anything) and the types of the input values.
Presently, input values are assumed to share the same type, and thus result types are only evaluated once (rather than per key) as well.
actually scrap that. it was too late for me last night, and was not thinking right. this is not about generic inference from mapped types. this is about having the function somewhere in the template so that it applies to every property as it is being created. not sure how though..
but i guess we need is somehow apply the function for each value in the template:
Note that a solution here would also extend to allowing Array.prototype.map to be typed such as to handle heterogeneous arrays. Examples:
Tuples (contents similar enough to share a mapper, distinct enough to make it desirable to keep them apart)
Lists e.g. pets.map((pet: Pet) => pet instanceof Bird) could potentially return precise results on the type level already. Allowing results by item, where known, would better accommodate the fact that the typing system is becoming more granular.
Another issue here with the aggressive evaluation of the indexed access type is using another type parameter, for instance, trying to define Ramada.path:
Mohamed Hegazy (@mhegazy): I tried to see if I could figure out a way to formulate map for tuples of length 2 as an easier version of this exercise: declare function map<A,B,T,U>(fn: (a: A) => B, tpl: [T,U]): [ typeof fn(T), typeof fn(U) ];
I'm not very confident about the requirements on typing fn here, particularly w.r.t. type constraints so as to guarantee T / U would match A. If the principle works though, I suppose a wall of typings with increasing numbers of generics could solve this for tuples, another version similar to your example hopefully for objects.
(If you have a good REPL for these things I'd be interested, right now I just tried a npm i -g on today's nightly of TypeScript, then tried testing in VSCode after setting this nightly as its TS version to use there, but it appeared to nevertheless see a syntax error in the function application.)
In your path example, maybe I can see what went wrong... you stated K1 extends keyof T, while you've defined T as the result, while it should rather be a key of the original object (or array technically). Perhaps it might make more sense like this?
declare function path<U, K1 extends keyof T, K2 extends keyof T[K1], T extends { [K1]: { [K2]: U } }>(keys: [K1, K2], obj: T): U;
If I try this in VSCode, on the K1/K2 (as used in the T definition) it gives me error squiggles 'K1' only refers to a type, but is being used as a value here. I'm still a bit stumped on that one.
If these can be addressed though, I'm hoping to solve the walls of 'definitions for ever-increasing numbers of generics' in my cross-linked reduce proposal.
Mohamed Hegazy (@mhegazy): I tried to think this over again, since most other TS issues I have could also be manually addressed by adding type annotations.
In your example, I'm a bit unfamiliar with the use of typeof at the type level, but what especially caught my eye in your example was your use of function application in the type language.
I've been under the impression this wasn't available in the current nightlies yet. This made me wonder, are you aware of any existing proposals to add this?
I'm now under the impression it could just suffice to address the problem here:
declare function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: F(T[P]) };
(Note I referred to the function here using its type, F, rather than its name, fn, as you had. My rationale here was that its type should include the relevant information, while its name might be unavailable in certain contexts, e.g. if I wanted to write CurriedFunction2<F, T, { [P in keyof T]: F(T[P]) }>. Not sure it's a great example, but I hope it illustrates my line of thought.)
Activity
aluanhaddad commentedon Nov 20, 2016
This is an issue with the declaration files for ramda as they are specified in the linked repo. The new Mapped Types feature #12114 allows this pattern to be expressed very elegantly.
KiaraGrouwstra commentedon Nov 21, 2016
I hadn't been aware, thank you for pointing this out! :)
HerringtonDarkholme commentedon Nov 21, 2016
Hi, I think this might be relevant usage of delayed index access type for generic. More info here #11929 (comment).
Rambda's API is a perfect usage of that.
KiaraGrouwstra commentedon Nov 21, 2016
If you'd forgive a follow-up question (which I can take to SO if deemed inappropriate here), I'm trying to figure out how to generalize the
mapObjexample of aluanhaddad's Mapped Types link:... to enable types separated by key, while in this example we seem to have a single T/U for the whole (multi-key) function call.
So I get that this kind of separation could be achieved along the lines of example
type T5 = { [P in keyof Item]: Item[P] };, but what's getting me stumped here is how to combine this with the generic functionf: (x: T) => U. As in, I suppose U and T would at that point no longer have a single instance, but rather one per key. Would that still be possible?HerringtonDarkholme commentedon Nov 21, 2016
I don't know whether it is possible in the long term. But it is impossible for now.
You can try this for now. It's the best of status quo.
KiaraGrouwstra commentedon Nov 21, 2016
Thanks! That'll do for now then. :D
Mohamed Hegazy (@mhegazy), if this could still be considered an open feature request, would you perhaps reconsider the
questiontag?type map for objects as well as currently possible (preserves keys bu…
mhegazy commentedon Nov 21, 2016
Mapped types should enable these scenarios. The change needed is to update the declaration files. if there are other ones that are not covered please provide more information about why mapped types is not sufficient.
KiaraGrouwstra commentedon Nov 22, 2016
Mohamed Hegazy (@mhegazy): I apologize, I'll admit my original example ended up not covering the breadth of my intended result. I've tried to update it accordingly.
The essence here is covered by the
.. which HerringtonDarkHolme stated is not presently possible.
Note that this becomes relevant in my (updated) example as different keys end up with different value types, dependent on both the transforming function (e.g.
arrayifybut could be anything) and the types of the input values.Presently, input values are assumed to share the same type, and thus result types are only evaluated once (rather than per key) as well.
4 remaining items
mhegazy commentedon Nov 22, 2016
Anders Hejlsberg (@ahejlsberg) is working on adding inference from mapped types. so you should be able to write something like:
mhegazy commentedon Nov 22, 2016
actually scrap that. it was too late for me last night, and was not thinking right. this is not about generic inference from mapped types. this is about having the function somewhere in the template so that it applies to every property as it is being created. not sure how though..
but i guess we need is somehow apply the function for each value in the template:
KiaraGrouwstra commentedon Nov 26, 2016
Note that a solution here would also extend to allowing
Array.prototype.mapto be typed such as to handle heterogeneous arrays. Examples:pets.map((pet: Pet) => pet instanceof Bird)could potentially return precise results on the type level already. Allowing results by item, where known, would better accommodate the fact that the typing system is becoming more granular.mhegazy commentedon Nov 26, 2016
Another issue here with the aggressive evaluation of the indexed access type is using another type parameter, for instance, trying to define
Ramada.path:KiaraGrouwstra commentedon Nov 27, 2016
Mohamed Hegazy (@mhegazy): I tried to see if I could figure out a way to formulate
mapfor tuples of length 2 as an easier version of this exercise:declare function map<A,B,T,U>(fn: (a: A) => B, tpl: [T,U]): [ typeof fn(T), typeof fn(U) ];I'm not very confident about the requirements on typing
fnhere, particularly w.r.t. type constraints so as to guaranteeT/Uwould matchA. If the principle works though, I suppose a wall of typings with increasing numbers of generics could solve this for tuples, another version similar to your example hopefully for objects.(If you have a good REPL for these things I'd be interested, right now I just tried a
npm i -gon today's nightly of TypeScript, then tried testing in VSCode after setting this nightly as its TS version to use there, but it appeared to nevertheless see a syntax error in the function application.)In your path example, maybe I can see what went wrong... you stated
K1 extends keyof T, while you've definedTas the result, while it should rather be a key of the original object (or array technically). Perhaps it might make more sense like this?declare function path<U, K1 extends keyof T, K2 extends keyof T[K1], T extends { [K1]: { [K2]: U } }>(keys: [K1, K2], obj: T): U;If I try this in VSCode, on the K1/K2 (as used in the T definition) it gives me error squiggles
'K1' only refers to a type, but is being used as a value here. I'm still a bit stumped on that one.If these can be addressed though, I'm hoping to solve the walls of 'definitions for ever-increasing numbers of generics' in my cross-linked reduce proposal.
mhegazy commentedon Nov 28, 2016
@tycho01 these are not fixed yet, and this is what this issue is tracking.
also seems to be a duplicate of #12342
KiaraGrouwstra commentedon Nov 29, 2016
I'll track that. Thank you. :)
KiaraGrouwstra commentedon Dec 5, 2016
Mohamed Hegazy (@mhegazy): I tried to think this over again, since most other TS issues I have could also be manually addressed by adding type annotations.
In your example, I'm a bit unfamiliar with the use of
typeofat the type level, but what especially caught my eye in your example was your use of function application in the type language.I've been under the impression this wasn't available in the current nightlies yet. This made me wonder, are you aware of any existing proposals to add this?
I'm now under the impression it could just suffice to address the problem here:
declare function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: F(T[P]) };(Note I referred to the function here using its type,
F, rather than its name,fn, as you had. My rationale here was that its type should include the relevant information, while its name might be unavailable in certain contexts, e.g. if I wanted to writeCurriedFunction2<F, T, { [P in keyof T]: F(T[P]) }>. Not sure it's a great example, but I hope it illustrates my line of thought.)mhegazy commentedon Dec 5, 2016
There is no way now to expresses the return type of a function. we have an issue #6606 tracking supporting this using
typeof <expr>.