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
Enable extraction of type parameters with a utility function called TypeParameters.
./privateApi.ts
exporttypeIChunk=<T>(array : T[],length : number)=>T[][];exporttypeIChunkTypeParameter=TypeParameters<IChunk>[0];// this is actually T
π Motivating Example and π» Use Cases
./chunk.js
/**@type {import("./privateApi").IChunk}*/constchunk=(array,length)=>{/** * @type {(typeof array)[]} * I do not like having logic inside the type JSDoc tag. * It can easily get ugly for more complicated cases. * I want to be able to import the type for `toReturn`. * For that I need `TypeParameters` to be enabled. */consttoReturn=[];// some code that calculates `toReturn`returntoReturn;}
Question
I would like to know whether the design of TypeScript is such, that TypeParameters is possible.
I donβt understand what IChunkTypeParameter would even be. T is an unbound type parameter in this context, there is no type assigned to it. Itβs like asking what the value of a function parameter is before you call the function, itβs nonsensical.
The βvalueβ in this case is the type assigned to T, so no, it doesnβt make sense - what do you expect IChunkTypeParameter to even be? It canβt be a type since the generic hasnβt been instantiated yet, so youβre going to have to give an example of how this would be used.
lillallol Pretty sure this isn't possible, at least not as you're suggesting. The big issue is that IChunkTypeParameter only has meaning contextually - it only makes even potentially sense inside of an IChunk annotated function. But types can't be contextual like that.
What would it mean if someone wrote code like:
// MyModule.tsimport{IChunkTypeParameter}from"lib";// What type is x here?constx: IChunkTypeParameter="is this allowed?";
Generally, I'd recommend avoiding the "type alias for a generic function" pattern in favor of generic functions, where possible. Instead of code like:
There's some cases where extracting the whole type of the function out into its own type can be useful, but I think they're somewhat rare.
(I used TS syntax throughout, because I'm way more comfortable with it - I don't think any of this problem is really unique to JS + JSDoc types syntax)
it only makes even potentially sense inside of an IChunk annotated function.
Yes it makes sense only inside a chunk annotated function.
What would it mean if someone wrote code like:
// MyModule.tsimport{IChunkTypeParameter}from"lib";// What type is x here?constx: IChunkTypeParameter="is this allowed?"
Of course this is not allowed since it is not possible to know whether T is of type string. Generics should be treated as read only anyway. You can of course do something like this:
typeIChunkToReturn=IChunkTypeParameter[][];
Generally, I'd recommend avoiding the "type alias for a generic function" pattern in favor of generic functions, where possible.
Why?
I don't think any of this problem is really unique to JS + JSDoc types syntax
Yes it makes sense only inside a chunk annotated function.
I think this makes this suggestion pretty much a non-starter. I'm pretty sure there aren't any examples of a type that works like this: having the type alias's type be implicitly contextual to where it's used is rather strange, so yes, I'd say it's probably close to "impossible".
Certainly, I suspect it would take a far more compelling reason than "it's slightly more convenient in this fairly niche case".
Generally, I'd recommend avoiding the "type alias for a generic function" pattern in favor of generic functions, where possible.
Why?
Mostly I avoid annotating whole function types because it's a lot more convenient, less repetitive:
constidentity: <T>(x: T)=>T=(x)=>x;// a lot more 'noisy' than the equivalent:constidentity=<T>(x: T)=>x;
But also in part because of stuff like this. You just don't need something like TypeParameters if the generic parameter isn't 'hidden' in a non-generic type alias.
Mostly I avoid annotating whole function types because it's a lot more convenient, less repetitive:
This can happen only when you write both concretions and types in .ts files. When you are writing types in .ts files and import them via JSDoc imports in .js files (because you want to avoid compiling) you can not do that (you actually can access the T but it is ugly and painful). You are also mixing implementation with intend, which I generally try to avoid because of many reasons.
Certainly, I suspect it would take a far more compelling reason than "it's slightly more convenient in this fairly niche case".
Look I can understand that using TypeScript without the need to compile is something that is not widely adopted, and maybe TypeScript maintainers will not give high priority for such features. I would like to have a clear answer from the TypeScript maintainers though, on whether the feature is intrinsically impossible due to TypeScript design.
This is conceptually unmoored in a number of ways. This isn't even about TypeScript; AFAIK any language with type parameters does not enable this sort of operation.
An unresolved type parameter appearing outside a generic context is meaningless, especially one attached to a function type
Asking for "the type parameters" of something is also meaningless in a structural type system. Once a generic type has been instantiated, you generally can't "go back" in a way that doesn't introduce assymmetries
In cases where this would make sense you can use something like type GetFoo<T> = T extends { foo: infer U } ? U : never;
An unresolved type parameter appearing outside a generic context is meaningless
Asking for "the type parameters" of something is also meaningless in a structural type system.
I explained how it is not meaningless based on the initial example I provided but also the conversation I had in the previous comments. Is that not enough?
Also the type parameter is virtually outside a generic context. All I am asking is that the context that enables us to use T here:
constchunk=<T>(array : T[],length : number)=>{consttoReturn : T[][]=[];// calculation of toReturn omitted for brevityreturntoReturn;}
to be enabled via something like TypeParameters<typeof chunk>[0]. Of course the extracted type parameter can be used to define other types, however they would make sense only inside the definition of chunk. By the way does using typeof makes it easier for TypeScript?
Once a generic type has been instantiated, you generally can't "go back" in a way that doesn't introduce assymmetries
How does that relate to my feature request? Care to elaborate with an example? Are you referring to more involved cases?
In cases where this would make sense you can use something like type GetFoo = T extends { foo: infer U } ? U : never;
Yes in my projects, for the initial example, I did use infer array element inside a JSDoc type tag. Then I realized I can just use /**@type {array[]}*/. But the JSDoc @type tag can get really ugly for more involved cases. TypeParameters will solve that.
constchunk=<T>(array : T[],length : number)=>{consttoReturn : T[][]=[];// calculation of toReturn omitted for brevityreturntoReturn;}
This example keeps getting posted, but you haven't yet given any examples of how TypeParameters (the actual requested feature) would be used in practice. All you've brought up is the idea of using TypeParameters<typeof chunk>[0] in place of T here, but what problem does that actually solve that can't already be solved by... just making the function generic and using T?
I get that you don't like any of the existing solutions, but the proposed idea here is not a viable solution because it would introduce a type that's completely meaningless outside of the context where the type should have been declared in the first place.
but what problem does that actually solve that can't already be solved by... just making the function generic and using T?
Good question. Here is the answer:
I want to use TypeScript without the need to compile, since that makes DX better.
I do want to separate intend and implementation, since that makes me need no documentation generation library for my libraries, no d.ts generator, but also makes my project more maintainable. I can give an example if you want.
If you try to adhere to these two points then you will realize that you need a feature like TypeParameters (and strong contextual typing but this off topic).
/**@type {import("./privateApi").IChunk}*/exportconstchunk=(array,length,toReturn=[])=>{/* some code that calculates `toReturn` */returntoReturn;}
Although that might not feel hacky for the context example, I have faced other examples where the extra parameter serves only the function definition and it is not supposed in any case to be provided a value by the consumer of the function. This can be solved by defining wrapper functions that hide the parameters that are not supposed to be exposed. For the example I have provided this is done like this:
/**@type {import("./privateApi").IChunkPrivate}*/exportconstchunkPrivate=(array,length,toReturn=[])=>{/* some code that calculates `toReturn` */returntoReturn;};/**@type {import("./privateApi").IChunk}*/exportconstchunk=(...parameters)=>chunkPrivate(...parameters);
A feature like TypeParameters would prevent these hacks.
No. We "could" do this (indeed, bugs have existed where you could accidently make this happen), but we're not going to, because it doesn't make any sense to allow a type parameter to escape its enclosing context, because outside of that context, there's no meaning to give those types.
I was looking for something similar today, but to extract the part after extends in a type parameter, to allow for creating a wrapper around a generic function:
importlibfrom'lib'exportfunctionmyLib<LibTypeParamextendsTypeParameters<typeoflib>[0]>(
...libParameters: Parameters<typeoflib>){/* ... do something custom ... */returnlib<LibTypeParam>(...libParameters);}
constchunk=<T>(array : T[],length : number):T[][]=>{consttoReturn=[];// calculation of toReturn omitted for brevityreturntoReturn;}
can a static type system (not just TypeScript) be made to infer the type of const toReturn = []; because of return toReturn, since the return type of chunk is known? That would reduce the need for extracted type parameters.
Activity
fatcerberus commentedon May 14, 2022
I donβt understand what
IChunkTypeParameterwould even be.Tis an unbound type parameter in this context, there is no type assigned to it. Itβs like asking what the value of a function parameter is before you call the function, itβs nonsensical.lillallol commentedon May 15, 2022
Well I made my case already why it makes sense. Also you are not asking what the value is. You are just assigning a label to it.
fatcerberus commentedon May 15, 2022
The βvalueβ in this case is the type assigned to
T, so no, it doesnβt make sense - what do you expectIChunkTypeParameterto even be? It canβt be a type since the generic hasnβt been instantiated yet, so youβre going to have to give an example of how this would be used.Retsam commentedon May 15, 2022
lillallol Pretty sure this isn't possible, at least not as you're suggesting. The big issue is that
IChunkTypeParameteronly has meaning contextually - it only makes even potentially sense inside of anIChunkannotated function. But types can't be contextual like that.What would it mean if someone wrote code like:
Generally, I'd recommend avoiding the "type alias for a generic function" pattern in favor of generic functions, where possible. Instead of code like:
I'd usually recommend:
There's some cases where extracting the whole type of the function out into its own type can be useful, but I think they're somewhat rare.
(I used TS syntax throughout, because I'm way more comfortable with it - I don't think any of this problem is really unique to JS + JSDoc types syntax)
lillallol commentedon May 15, 2022
Retsam
Yes it makes sense only inside a chunk annotated function.
Of course this is not allowed since it is not possible to know whether
Tis of typestring. Generics should be treated as read only anyway. You can of course do something like this:Why?
I do not understand this sentence.
Bruce Pascoe (@fatcerberus)
I already answered that.
IChunkTypeParameteris the first type parameter ofIChunk.I already did. Provide extra cases like Retsam and I would gladly answer.
Retsam commentedon May 15, 2022
lillallol
I think this makes this suggestion pretty much a non-starter. I'm pretty sure there aren't any examples of a type that works like this: having the type alias's type be implicitly contextual to where it's used is rather strange, so yes, I'd say it's probably close to "impossible".
Certainly, I suspect it would take a far more compelling reason than "it's slightly more convenient in this fairly niche case".
Mostly I avoid annotating whole function types because it's a lot more convenient, less repetitive:
But also in part because of stuff like this. You just don't need something like
TypeParametersif the generic parameter isn't 'hidden' in a non-generic type alias.lillallol commentedon May 15, 2022
This can happen only when you write both concretions and types in
.tsfiles. When you are writing types in.tsfiles and import them via JSDoc imports in.jsfiles (because you want to avoid compiling) you can not do that (you actually can access theTbut it is ugly and painful). You are also mixing implementation with intend, which I generally try to avoid because of many reasons.Look I can understand that using TypeScript without the need to compile is something that is not widely adopted, and maybe TypeScript maintainers will not give high priority for such features. I would like to have a clear answer from the TypeScript maintainers though, on whether the feature is intrinsically impossible due to TypeScript design.
Retsam commentedon May 15, 2022
lillallol You can still write the equivalent code in JS files, though:
lillallol commentedon May 15, 2022
Yes you are right. Here are my objections though:
I do consider the benefits that occur from these two reasons (especially the second one) enough for this feature to be adopted.
RyanCavanaugh commentedon May 16, 2022
This is conceptually unmoored in a number of ways. This isn't even about TypeScript; AFAIK any language with type parameters does not enable this sort of operation.
In cases where this would make sense you can use something like
type GetFoo<T> = T extends { foo: infer U } ? U : never;lillallol commentedon May 16, 2022
Ryan Cavanaugh (@RyanCavanaugh)
I explained how it is not meaningless based on the initial example I provided but also the conversation I had in the previous comments. Is that not enough?
Also the type parameter is virtually outside a generic context. All I am asking is that the context that enables us to use
There:to be enabled via something like
TypeParameters<typeof chunk>[0]. Of course the extracted type parameter can be used to define other types, however they would make sense only inside the definition ofchunk. By the way does usingtypeofmakes it easier for TypeScript?How does that relate to my feature request? Care to elaborate with an example? Are you referring to more involved cases?
Yes in my projects, for the initial example, I did use infer array element inside a JSDoc type tag. Then I realized I can just use
/**@type {array[]}*/. But the JSDoc@typetag can get really ugly for more involved cases.TypeParameterswill solve that.fatcerberus commentedon May 16, 2022
This example keeps getting posted, but you haven't yet given any examples of how
TypeParameters(the actual requested feature) would be used in practice. All you've brought up is the idea of usingTypeParameters<typeof chunk>[0]in place ofThere, but what problem does that actually solve that can't already be solved by... just making the function generic and usingT?RyanCavanaugh commentedon May 16, 2022
I get that you don't like any of the existing solutions, but the proposed idea here is not a viable solution because it would introduce a type that's completely meaningless outside of the context where the type should have been declared in the first place.
lillallol commentedon May 17, 2022
Ryan Cavanaugh (@RyanCavanaugh)
So we are dealing with a design limitation?
Bruce Pascoe (@fatcerberus)
Good question. Here is the answer:
d.tsgenerator, but also makes my project more maintainable. I can give an example if you want.If you try to adhere to these two points then you will realize that you need a feature like
TypeParameters(and strong contextual typing but this off topic).lillallol commentedon May 18, 2022
By the way I would like to mention that for the example I have provided, there is a solution like this:
./privateApi.ts./chunk.jsAlthough that might not feel hacky for the context example, I have faced other examples where the extra parameter serves only the function definition and it is not supposed in any case to be provided a value by the consumer of the function. This can be solved by defining wrapper functions that hide the parameters that are not supposed to be exposed. For the example I have provided this is done like this:
./privateApi.ts./chunk.jsA feature like
TypeParameterswould prevent these hacks.RyanCavanaugh commentedon May 18, 2022
No. We "could" do this (indeed, bugs have existed where you could accidently make this happen), but we're not going to, because it doesn't make any sense to allow a type parameter to escape its enclosing context, because outside of that context, there's no meaning to give those types.
karlhorky commentedon Jun 7, 2023
I was looking for something similar today, but to extract the part after
extendsin a type parameter, to allow for creating a wrapper around a generic function:karlhorky commentedon Jun 7, 2023
Workaround
The workaround that we found for replicating the wrapper pattern above was to use casting, although I'm not sure if this works in every case:
lillallol commentedon Jul 1, 2023
Ryan Cavanaugh (@RyanCavanaugh) By the way for the following code snippet:
can a static type system (not just TypeScript) be made to infer the type of
const toReturn = [];because ofreturn toReturn, since the return type ofchunkis known? That would reduce the need for extracted type parameters.