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
Allow skipping some generics when calling a function with multiple generics #10571
Right now in TypeScript it's all or nothing when calling generic methods. You can either skip typing all the generics altogether and they will be inferred from the context (if possible), or you have to manually (re)define all of them. But the reality isn't black and white, there are also shades of gray, where we can infer types for some of the generic parameters, but not others. Currently those have to be unnecessarily verbose by forcing the programmer to explicitly restate them.
Take a look at these 3 cases:
Case 1 - everything can be inferred - no need to call the method with <> definition:
Case 3 - the one that's interesting to this feature request - some can be inferred, some can't:
functioncase3<A,B,C>(b: string,c: boolean): A{}// incorrect type of A - left unspecified:example('thing');// correct, but unnecessarily verbose - we already know that 'thing' is a string and true is a boolexample<number,string,boolean>('thing',true);
Now, typing string, boolean in the above example isn't a big deal, but with complex scenarios, say with a method using 5 generics, where you can infer 4 of them, retyping them all seems overly verbose and prone to error.
It would be great if we could have some way to skip re-typing the types that can automatically be inferred. Something like a special auto or inferred type, so we could write:
example<number,auto,auto>('thing',bool);
Or maybe even, if we only want to specify those up to a certain point:
example<number>('thing',bool);
The above "short-hand" notation could perhaps be different to account for function overloads with different number of generics.
Mohamed Hegazy (@mhegazy) I don't think #2175 covers this. In fact, both propositions complement each other quite nicely. The proposed "Default generic type variables" extends the "all or nothing" notion of generic usage and deals with the way the class/function producer specifies them, not the way the class/function consumer uses them. In usage, you are still left with either omitting all generic parameters or specifying all explicitly. The only thing #2175 changes is the fallback type ({}) when it cannot be automatically inferred by the compiler.
This issue deals with the possibility of omitting some type parameters for automatic inference, while specifying others, not with defining fallback defaults.
Hope that's clearer.
We already have a paradigm for passing arguments to functions, including default arguments in ES6+ in TypeScript/JavaScript. Why invent a new semantic? Why would generics just not follow the same semantics.
Kitson Kelly (@kitsonk) You would still have to introduce an undefined type for non-last arguments (in ES6+ this is how you would use the default on non-last arguments).
The proposed * / auto is just that -- without the ugly sounding undefined which is also a type, now that we have strict null checks.
No... you could just skip them, like array destructuring: < , , Bar>
Reacted by Michael Sanford, Paulo Cesar, Han Seoul-Oh, Thomas Hasner, kamijin_fanta, Roman Starkov, Jed, Dave Houlbrooke, John Cido, electrovir and 36 more
Kitson Kelly (@kitsonk) sure, coma-style skipping is an option too. However in your original post you argued for "default arguments" semantics, not array destructuring semantics.
Ultimately I'm okay with either semantic, < , , Bar> or <*, *, Bar>.
Reacted by Michael Sanford, Han Seoul-Oh, Alec Larson, John Cido, electrovir, Justin Dalrymple, Kier Borromeo, Patricio Ezequiel Hondagneu Roig, Noah, Avshalom Toren and 4 more
I personally find this very hard to read. specially with long argument list, something like foo<, , ,A, , D>() was that right? or was it foo<, , ,A, D, >() .
#2175 puts this on the declaration. you have to decide as an interface author which type parameters are optional, and what are the defaults, and you put them at the end.
also note that generic type arguments is modeled after function arguments. it is illegal to call a function with missing arguments, or with less parameters than the signature requires.
Reacted by Michael Sanford, Aluan Haddad, insidewhy, Alexander CherryTea, Fardad Mohammadi, edify-jashmore, Tareq Dayya, Leon Adler, Ilan Segal and Eric Bieszczad-Stie
Reacted by Luke Tanner, eryn L. K., Anton Shakirzianov, Memeplexx, Riccardo Attilio Galli, Bohdan, Victor St-Louis, Arad Alvand, Dmitry Burlaka, Erfan Azary and 3 more
Mohamed Hegazy (@mhegazy) the problem is as the interface author you cannot always reliably put them at the end. Sometimes you might need to force the use of the last argument, while the penultimate is inferred. That's why we need to be able to choose which are to be inferred - as the consumer.
Indeed it is illegal to call with missing arguments, that's why we're proposing an "infer" argument - equivalent of undefined - the empty space or *. You do make a point with coma skip being hard to read with long lists of arguments -- I'm going to back the * proposed by Basarat Ali Syed (@basarat).
Can you provide an example, considering TypeScript allows overrides, where you feel this cannot be accomplished?
I would expect Bazyli Brzóska (@niieani) wants to keep the type parameter in the same order as the regular parameters. so in this sense it is not always possible to move them around if you do not control the actual function signatures.
Mohamed Hegazy (@mhegazy) that's one reason, but actually there's another one.
I came across this problem while writing type declarations for RethinkDB. The definitions are incredibly complex and I remember being unable to implement certain features exactly because of the fact that certain classes would have to use up to 4-8 generics (as a workaround, because of other TS limitations). Each generic type would be a pre-formed object based on the input T, so that we can keep track of the original(s) while transforming the object being passed through (notably the way RethinkDB's group() and ungroup() methods work).
The end-user only needs to be able to consume the methods by passing one or two generic arguments at most, not all of them -- the point is I don't want to burden the user from having to re-type all the generics that are an implementation detail. But ultimately non-last arguments are not a major problem for the end-user, it's my problem as the type-definition/library creator, as not being able to type only the specific one or two type parameters creates a maintenance nightmare!
Output of every single method would require me to type and re-type those same generic types over and over again, while most of them could be inferred and only some need manual adjustment in the output.
I don't remember the exact code example right now as I was working on the typings around February, but if I start working on it again, I'll post one here.
btoo You've missed the point of this issue. I'll illustrate with Case 3 from the original post:
functioncase3<A,B,C>(b: B,c: C): A{}// incorrect type of A - left unspecified:example('thing');// correct, but unnecessarily verbose - we already know that 'thing' is a string and true is a boolexample<number,string,boolean>('thing',true);
As there's been silence here for 6 months, I'll add my +1. This feature would simplify things greatly for a library feature I'm working on. I need to pass one required type parameter which can't be inferred, but I have 3 (so far) other type parameters which can easily be inferred from arguments.
This is extra annoying in a case like mine where the types are more complex. Here is a simplified example, setting up a React hook for filtering items in a table:
interfaceFilterCategory<T,K>{key: K;getFieldValue: (item: T)=>string;someMoreOptions?: {/* things that need to reference the T and K types */}};constuseFilterControls=<T,K>(args: {categories: FilterCategory<T,K>,someMoreArgs: {/*...*/}})=>{/*...*/};
Here is the desired usage, passing in Customer for T and letting K be inferred from the key values of the objects in my categories array on my args object:
constcontrols=useFilterControls<Customer>({categories: [{key: 'name',getFieldValue: (customer)=>customer.name},{key: 'address',getFieldValue: (customer)=>formatAddress(customer.address)},{key: 'phone',getFieldValue: (customer)=>customer.phone.preference==='mobile' ? customer.phone.mobile : customer.phone.home},// Imagine many more categories here],someMoreArgs: {/*...*/}});
But that doesn't work, it requires either both T and K be passed in or neither. I have to do this:
constcontrols=useFilterControls<Customer,'name'|'address'|'phone'>({categories: [{key: 'name',getFieldValue: (customer)=>customer.name},{key: 'address',getFieldValue: (customer)=>formatAddress(customer.address)},{key: 'phone',getFieldValue: (customer)=>customer.phone.preference==='mobile' ? customer.phone.mobile : customer.phone.home},// Imagine many more categories here],someMoreArgs: {/*...*/}});
I know for this example I could omit both type params and use getFieldValue: (customer: Customer) => ..., but (a) that shouldn't be necessary anyway and (b) there are things that I'm omitting here that rely on the inferred type and are a lot harder to fix than this.
This is very simplified -- some of our tables have 10-15 filter categories, that string union would have to repeat all 10-15 keys. We also have other functions related to sorting, pagination, selection, expandable rows, and other table state stuff that all have their own sets of 2-4 generic type parameters and rely on the same single T (the type of the objects we're describing in the table). Wrapping them all in one function requires passing in several lines of just type parameters when we should be able to just pass in <Customer> and infer the rest.
Reacted by Ian Bolton, Bo Lingen, Guilherme Caminha, Tyren, Verner Codoceo, Paweł and Alexander Sinitsyn
Mike Turley (@mturley) We're all looking forward to a future with this proposed feature. FWIW — in your example K can be keyof T by default — based on the details you shared, you can create your desired API without this feature:
typeFilterCategory<T,KextendskeyofT>={key: K;getFieldValue: (item: T)=>string;someMoreOptions?: {/* things that need to reference the T and K types */}};functionuseFilterControls<T,KextendskeyofT=keyofT>(args: {categories: FilterCategory<T,K>[];someMoreArgs: {/*...*/};},){/*...*/};// Here are some stand-ins for the missing types in your example:typeAddress=unknown;declarefunctionformatAddress(address: Address): string;typeCustomer={address: Address;name: string;phone: Record<"preference"|"mobile"|"home",string>;};constcontrols=useFilterControls<Customer>({categories: [{key: "name",getFieldValue: (customer)=>customer.name},{key: "address",getFieldValue: (customer)=>formatAddress(customer.address)},{key: "phone",getFieldValue: (customer)=>customer.phone.preference==="mobile" ? customer.phone.mobile : customer.phone.home},// Imagine many more categories here],someMoreArgs: {/*...*/}});
Thanks! Yeah, the example I wrote up is bad, there's not a 1:1 mapping of filter keys to the keys in T and there are some more wonky types in our full implementation that don't match up nicely like that. But I appreciate the advice and there are definitely some things we can simplify along those lines.
woah! i got ratio'd super hard on this thread. well i can't say I'm not disappointed, but I'm not so much disappointed in getting ratio'd as much as I am in the commenters/lurkers in this thread for favoring their own emotional attachment over meaningful engagement
functioncase3<A,B,C>(b: B,c: C): A{}// incorrect type of A - left unspecified:example('thing');// correct, but unnecessarily verbose - we already know that 'thing' is a string and true is a boolexample<number,string,boolean>('thing',true);
why they're still unhappy. giving them the benefit of the doubt that they didn't already know this code was littered with issues, we can say for a moment they eventually arrived at an actually workable reproduction, which might've looked something like
declarefunctionexample<A,B,C>(b: B,c: C): A// incorrect type of A - left unspecified:example('thing');// correct, but unnecessarily verbose - we already know that 'thing' is a string and true is a boolexample<number,string,boolean>('thing',true);
at this point, they could have correlated this to
my directly addressing this
but instead, I mostly just saw more tired complaining about how they've been waiting forever and the typescript maintainers still haven't given them exactly what they're asking for yet
to spell it out, my interpretation of this feature request is to have a way that:
allows us to provide to a generic function (or ideally also a type) any number of type arguments less than the entire list
does the above without leaving any runtime footprint
and while there already exists a workaround as described in https://github.com//issues/10571#issuecomment-326761563, that workaround leaves a runtime footprint whereas mine does not
anyway I hope I'm wasting my breath here defending the use of satisfies and instantiation expressions because a typescript maintainer opened #54047 for adding this feature recently
Reacted by Ivan Seidel
Reacted by Connor and max
Reacted by Karol Woźniak, Mike Turley, Bo Lingen, Guilherme Caminha, Nick Chevsky, Gabriele Tomberli, Torvin, Damjan Polugic and Sibbe
btoo I can't say for others, but I am subscribed and emoted threads like that, because I just can't use workaround, (I use it a lot in some cases, and I am very satisfied by satisfy... But not for this case)
And every time, when I need write tricky/costly workaround, I think - "Why I write this ... instead of simple and clean code? It's really technical restriction? Or may be just feat. that doesn't counted as essential by maintainer because too small community reaction?"
And as external observer who can't really estimate this feat. difficulty I just emote/post to thread with hope to make this feature a bit hotter on backlog...
And about workaround itself, this example just simplified fragment, but real code can be terrible itself and far more terrible with workarounds like this...
We live not in a fairytale, and this feature with and or something can be easily used by people who almost don't know TS, but satisfies with ReturnType just blowup his mind, and this case cause us use an easy and untyped code instead of easy and well-typed.
And sorry for my English and may be too emotional response.
btoo you are correct that I and others here hadn't realized using satisfies with any in place of the inferred generics works for some of these cases, and I do appreciate you reiterating that. I'll use that workaround for now. But a couple of points:
I think folks interpreted your suggestion not as a temporary workaround but as a solution that makes this feature request unnecessary. This may have been an unfair assumption, but it was unclear IMO.
The use of explicit any is considered by many to be an antipattern, and while I understand that in this case it isn't problematic (it doesn't result in any actual variables receiving the any type) it introduces a maintenance problem (I now need to pay attention to which anys are acceptable and which are not, instead of avoiding it entirely). Let alone the syntax being unnecessarily verbose. The simplified syntax in this proposal definitely has merit.
I find it difficult to take your accusation of emotional attachment seriously when you seem to exhibit it yourself. I agree that some other comments in this thread are rude, but yours is among them. Statements like saying someone's example code is "littered with issues" when it was clearly a rough illustration of the issue (and it succeeded in doing so) are not helpful, and strange to see when your message also has formatting issues and contains a malformed URL. (Yes I do see the irony in me pointing this out). We don't need to expect every line of a message here to be perfect.
My takeaways here:
The feature request is still a valid one, and it is understandable that people are annoyed it has been ignored for so long.
The satisfies workaround is valid for some cases but not all, and both of you are right: it shouldn't be dismissed, but it shouldn't invalidate the request for the full feature.
I apologize if my comment makes things worse rather than better, I acknowledge that it is also coming from an emotional place. I just wish things like this didn't always devolve into hurt feelings.
Reacted by Bo Lingen, Nick Chevsky, Ethan, abnessor aka findoff, Ian Bolton, Jesse Niininen, Victor Hallberg, Gabriele Tomberli, Tyren, Verner Codoceo and 7 more
Thanks Mike Turley (@mturley) - that is largely accurate from my side. I am guilty of that emotional response - I'm subscribed to several issues and many (some related to this, some not) received similar replies from btoo regarding satisfies, so it felt like a "when you have a hammer, everything looks like a nail" situation and was being used to dismiss this issue instead of as a "here's something that may help while this issue remains open".
btoo thank you for re-emphasizing the use of any in your snippets - that usage was lost on me (I interpreted them more as placeholders than as literal anys), so you're right that satisfies can be a valid (albeit verbose) workaround here.
Ethan (@Matchlighter) To be fair, the first time I read btoo 's response, I also did not notice that the use of any would solve this issue. It doesn't feel like an intuitive use of the any keyword at first.
FWIW, I tried the satisfies solution and some things are still failing to be inferred (it appears to use default values for type params that have a default? not sure if that's what's really going on). It's difficult to create an isolated example to reproduce this. I'll be curious if the new implementation fixes it or has the same behavior.
There is a solution that is quite verbose on the declaration, but less verbose than current options on the usage: using an type mapper object type:
typeArgs<A,B,C>={a?: Ab?: Bc?: C}functionexample<TArgsextendsArgs<A,B,C>,A=any,B=any,C=any>(b: TArgs['b'],c: TArgs['c']): TArgs['a']{returnnullasany}example('thing',true)// returns anyexample<{a: number}>('thing',true)// returns numberexample<{b: number}>('thing',true)// fails for incompatible argument 'b'example<{a: number;c: number}>('thing',true)// fails for incompatible argument 'c', but returns number
With such solution, you can also be stricter on each type as such:
// Constrained exampletypeArgs2<Receivesextendsnumber,Returnsextendsstring>={receives?: Receivesreturns?: Returns}functionconstrained<TArgsextendsArgs2<Receives,Returns>,Receivesextendsnumber=number,Returnsextendsstring=string>(param: TArgs['receives']extendsnumber ? TArgs['receives'] : number): TArgs['returns']extendsstring ? TArgs['returns'] : string{returnnullasany}constrained()// fails for missing argumentconstrained('a string')// fails for incompatible argumentconstrained(1)// works with default typesconstrained<{receives: 10}>(1)// fails for incompatible argumentconstrained<{receives: 10}>(10)// works with specific typed argumentconstrained(1)// returns stringconstrained<{returns: 'specific'}>(1)// returns 'specific' value
Hope this helps someone while a native solution isn't in place. Check the playground
Reacted by Homa Wong, noom, Torvin, Alexis Tyler, Alvis Tang, Tyren, Jacob Mischka, Thodoris Greasidis, Zdenek Kostal, Piotr Witek and 24 more
Activity
mhegazy commentedon Aug 26, 2016
I would say covered by #2175
basarat commentedon Aug 26, 2016
autoI would say*as auto might be a type name (unlikely but still). Also shorter 🌹niieani commentedon Aug 27, 2016
Mohamed Hegazy (@mhegazy) I don't think #2175 covers this. In fact, both propositions complement each other quite nicely. The proposed "Default generic type variables" extends the "all or nothing" notion of generic usage and deals with the way the class/function producer specifies them, not the way the class/function consumer uses them. In usage, you are still left with either omitting all generic parameters or specifying all explicitly. The only thing #2175 changes is the fallback type (
{}) when it cannot be automatically inferred by the compiler.This issue deals with the possibility of omitting some type parameters for automatic inference, while specifying others, not with defining fallback defaults.
Hope that's clearer.
I also like Basarat Ali Syed (@basarat)'s proposed
*instead ofauto.kitsonk commentedon Aug 27, 2016
We already have a paradigm for passing arguments to functions, including default arguments in ES6+ in TypeScript/JavaScript. Why invent a new semantic? Why would generics just not follow the same semantics.
niieani commentedon Aug 27, 2016
Kitson Kelly (@kitsonk) You would still have to introduce an
undefinedtype for non-last arguments (in ES6+ this is how you would use the default on non-last arguments).The proposed
*/autois just that -- without the ugly soundingundefinedwhich is also a type, now that we have strict null checks.kitsonk commentedon Aug 27, 2016
No... you could just skip them, like array destructuring:
< , , Bar>niieani commentedon Aug 27, 2016
Kitson Kelly (@kitsonk) sure, coma-style skipping is an option too. However in your original post you argued for "default arguments" semantics, not array destructuring semantics.
Ultimately I'm okay with either semantic,
< , , Bar>or<*, *, Bar>.mhegazy commentedon Aug 29, 2016
I personally find this very hard to read. specially with long argument list, something like
foo<, , ,A, , D>()was that right? or was itfoo<, , ,A, D, >().#2175 puts this on the declaration. you have to decide as an interface author which type parameters are optional, and what are the defaults, and you put them at the end.
also note that generic type arguments is modeled after function arguments. it is illegal to call a function with missing arguments, or with less parameters than the signature requires.
niieani commentedon Aug 29, 2016
Mohamed Hegazy (@mhegazy) the problem is as the interface author you cannot always reliably put them at the end. Sometimes you might need to force the use of the last argument, while the penultimate is inferred. That's why we need to be able to choose which are to be inferred - as the consumer.
Indeed it is illegal to call with missing arguments, that's why we're proposing an "infer" argument - equivalent of
undefined- the empty space or*. You do make a point with coma skip being hard to read with long lists of arguments -- I'm going to back the*proposed by Basarat Ali Syed (@basarat).kitsonk commentedon Aug 29, 2016
Can you provide an example, considering TypeScript allows overrides, where you feel this cannot be accomplished?
mhegazy commentedon Aug 29, 2016
I would expect Bazyli Brzóska (@niieani) wants to keep the type parameter in the same order as the regular parameters. so in this sense it is not always possible to move them around if you do not control the actual function signatures.
niieani commentedon Aug 29, 2016
Mohamed Hegazy (@mhegazy) that's one reason, but actually there's another one.
I came across this problem while writing type declarations for RethinkDB. The definitions are incredibly complex and I remember being unable to implement certain features exactly because of the fact that certain classes would have to use up to 4-8 generics (as a workaround, because of other TS limitations). Each generic type would be a pre-formed object based on the input T, so that we can keep track of the original(s) while transforming the object being passed through (notably the way RethinkDB's
group()andungroup()methods work).The end-user only needs to be able to consume the methods by passing one or two generic arguments at most, not all of them -- the point is I don't want to burden the user from having to re-type all the generics that are an implementation detail. But ultimately non-last arguments are not a major problem for the end-user, it's my problem as the type-definition/library creator, as not being able to type only the specific one or two type parameters creates a maintenance nightmare!
Output of every single method would require me to type and re-type those same generic types over and over again, while most of them could be inferred and only some need manual adjustment in the output.
I don't remember the exact code example right now as I was working on the typings around February, but if I start working on it again, I'll post one here.
niieani commentedon Dec 23, 2016
Flowtype's equivalent is
*- existential type. Read this for reference.74 remaining items
temoncher commentedon Sep 8, 2022
Luis Pais (@Ranguna)
Isn't it already scheduled for November 2022? https://github.com/microsoft/TypeScript/wiki/Roadmap#49-november-2022
The issue is already closed with #46827
Matchlighter commentedon Sep 8, 2022
btoo You've missed the point of this issue. I'll illustrate with Case 3 from the original post:
We'd like to write one of
You're suggesting we write
which is even more verbose than the workaround that exists.
satisfiesis not a solution to this.Misaka-0x447f commentedon Nov 24, 2022
Still waiting for this feature and for now this could be a solution if you only have one required type parameter and lots of inferred type parameters:
mturley commentedon May 1, 2023
As there's been silence here for 6 months, I'll add my +1. This feature would simplify things greatly for a library feature I'm working on. I need to pass one required type parameter which can't be inferred, but I have 3 (so far) other type parameters which can easily be inferred from arguments.
This is extra annoying in a case like mine where the types are more complex. Here is a simplified example, setting up a React hook for filtering items in a table:
Here is the desired usage, passing in
CustomerforTand lettingKbe inferred from thekeyvalues of the objects in mycategoriesarray on myargsobject:But that doesn't work, it requires either both T and K be passed in or neither. I have to do this:
I know for this example I could omit both type params and use
getFieldValue: (customer: Customer) => ..., but (a) that shouldn't be necessary anyway and (b) there are things that I'm omitting here that rely on the inferred type and are a lot harder to fix than this.This is very simplified -- some of our tables have 10-15 filter categories, that string union would have to repeat all 10-15 keys. We also have other functions related to sorting, pagination, selection, expandable rows, and other table state stuff that all have their own sets of 2-4 generic type parameters and rely on the same single
T(the type of the objects we're describing in the table). Wrapping them all in one function requires passing in several lines of just type parameters when we should be able to just pass in<Customer>and infer the rest.jsejcksn commentedon May 1, 2023
Mike Turley (@mturley) We're all looking forward to a future with this proposed feature. FWIW — in your example
Kcan bekeyof Tby default — based on the details you shared, you can create your desired API without this feature:TS Playground
mturley commentedon May 1, 2023
Thanks! Yeah, the example I wrote up is bad, there's not a 1:1 mapping of filter keys to the keys in T and there are some more wonky types in our full implementation that don't match up nicely like that. But I appreciate the advice and there are definitely some things we can simplify along those lines.
btoo commentedon May 2, 2023
woah! i got ratio'd super hard on this thread. well i can't say I'm not disappointed, but I'm not so much disappointed in getting ratio'd as much as I am in the commenters/lurkers in this thread for favoring their own emotional attachment over meaningful engagement
essentially ignoring the entirety of #10571 (comment), Ethan (@Matchlighter) and, by proxy, his 👍ers want to
why they're still unhappy. giving them the benefit of the doubt that they didn't already know this code was littered with issues, we can say for a moment they eventually arrived at an actually workable reproduction, which might've looked something like
this (playground link).
my directly addressing this
their claim that I'm suggesting they do something else
but instead, I mostly just saw more tired complaining about how they've been waiting forever and the typescript maintainers still haven't given them exactly what they're asking for yet
to spell it out, my interpretation of this feature request is to have a way that:
well
does exactly this if you just use
anyinstead of_,*,infer, orautoanyway I hope I'm wasting my breath here defending the use of
satisfiesand instantiation expressions because a typescript maintainer opened #54047 for adding this feature recentlyfindoff commentedon May 2, 2023
btoo I can't say for others, but I am subscribed and emoted threads like that, because I just can't use workaround, (I use it a lot in some cases, and I am very satisfied by satisfy... But not for this case)
And every time, when I need write tricky/costly workaround, I think - "Why I write this ... instead of simple and clean code? It's really technical restriction? Or may be just feat. that doesn't counted as essential by maintainer because too small community reaction?"
And as external observer who can't really estimate this feat. difficulty I just emote/post to thread with hope to make this feature a bit hotter on backlog...
And about workaround itself, this example just simplified fragment, but real code can be terrible itself and far more terrible with workarounds like this...
We live not in a fairytale, and this feature with and or something can be easily used by people who almost don't know TS, but satisfies with ReturnType just blowup his mind, and this case cause us use an easy and untyped code instead of easy and well-typed.
And sorry for my English and may be too emotional response.
mturley commentedon May 2, 2023
btoo you are correct that I and others here hadn't realized using
satisfieswithanyin place of the inferred generics works for some of these cases, and I do appreciate you reiterating that. I'll use that workaround for now. But a couple of points:anyis considered by many to be an antipattern, and while I understand that in this case it isn't problematic (it doesn't result in any actual variables receiving theanytype) it introduces a maintenance problem (I now need to pay attention to whichanys are acceptable and which are not, instead of avoiding it entirely). Let alone the syntax being unnecessarily verbose. The simplified syntax in this proposal definitely has merit.My takeaways here:
satisfiesworkaround is valid for some cases but not all, and both of you are right: it shouldn't be dismissed, but it shouldn't invalidate the request for the full feature.I apologize if my comment makes things worse rather than better, I acknowledge that it is also coming from an emotional place. I just wish things like this didn't always devolve into hurt feelings.
Matchlighter commentedon May 2, 2023
Thanks Mike Turley (@mturley) - that is largely accurate from my side. I am guilty of that emotional response - I'm subscribed to several issues and many (some related to this, some not) received similar replies from btoo regarding
satisfies, so it felt like a "when you have a hammer, everything looks like a nail" situation and was being used to dismiss this issue instead of as a "here's something that may help while this issue remains open".btoo thank you for re-emphasizing the use of
anyin your snippets - that usage was lost on me (I interpreted them more as placeholders than as literalanys), so you're right thatsatisfiescan be a valid (albeit verbose) workaround here.gpkc commentedon May 2, 2023
Ethan (@Matchlighter) To be fair, the first time I read btoo 's response, I also did not notice that the use of
anywould solve this issue. It doesn't feel like an intuitive use of theanykeyword at first.mturley commentedon May 2, 2023
FWIW, I tried the
satisfiessolution and some things are still failing to be inferred (it appears to use default values for type params that have a default? not sure if that's what's really going on). It's difficult to create an isolated example to reproduce this. I'll be curious if the new implementation fixes it or has the same behavior.chr-ge commentedon Oct 6, 2023
You could pass
neveroranyto the first argumentlucasconstantino commentedon Dec 31, 2023
There is a solution that is quite verbose on the declaration, but less verbose than current options on the usage: using an type mapper object type:
With such solution, you can also be stricter on each type as such:
Hope this helps someone while a native solution isn't in place. Check the playground
Add currying parenthesis to create