Description
Suggestion
π Search Terms
anonymous, type parameter, type parameters
β Viability Checklist
My suggestion meets these guidelines:
- [*] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [*] This wouldn't change the runtime behavior of existing JavaScript code
- [*] This could be implemented without emitting different JS based on the types of the expressions
- [*] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [*] This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
Whenever one wants to apply a generic type constraint, a full type must be provided.
The suggestion is that in a type/function definition Typescript could accept a generic type constraint for the property/argument, without having to declare a type parameter
π Motivating Example
interface Person<Profession extends Carpenter | Hacker | Judge> {
name: Exclude<extends string, "">;
profession: Profession;
employer?: Profession extends Hacker | Judge ? Person : never
}
const divide = (a: number, b: Exclude<extends number, 0>) => a/b;
const ping = (ip: extends `${number}.${number}.${number}.${number}` ? string : never) => {}
const definitelyReturnsTrue = (arg: Exclude<,undefined | null | false | "" | 0>) => !!arg;
π» Use Cases
When in a type/function definition, if wanting to use a generic constraint for a parameter/argument, one must declare a type parameter even though it doesn't get used anywhere else in the definition.
Personally I see myself using such a feature most when I'm writing complex functions that already have multiple type parameters declared, and while I really want to constrain a certain parameter (my most common use case is excluding "" from string arguments but I've had other uses too), I might forgo doing so, because adding another type parameter to the function significantly increases its' complexity (as I now have to mentally keep track of another parameter, which ends up only being used once).
The way I see it, adding a type parameter in the beginning of the function's definition means that parameter is meant to be used in several places within that function, like the Person interface's Profession example. If it's only used once, I generally shouldn't need a type parameter.
In the meantime, of course, I either add an extra type parameter which I'll only ever use once, or if I decide it's not worth the complexity, I give up on the desired type safety and go with the strictest non-generic constraint available to me (usually a primitive like string or number).
To be clear, I do assume that if using the function/interface's type, rather than calling it/constraining a new object literal, it will count as generic and require type parameters, as the language obviously can't infer the generic constraint's type parameter in that case. Typescript could maybe name the anonymous type parameter based on the argument/parameter it's used in?
Activity
whzx5byb commentedon Apr 7, 2022
A literal type constraint for
ping
could be defined by writing:const ping = (ip: `${number}.${number}.${number}.${number}`) => {}
And for other "exclude" examples you need to wait for the negated type:
const divide = (a: number, b: number & not 0) => a/b
lirannl commentedon Apr 7, 2022
Those are two interesting solutions, and while I will keep those in mind (especially the negated types) - this could still be useful for other things beyond Exclude<>, like a Partial<>, or ReturnType<extends (...args: unknown[]) => unknown>, so I don't believe these completely cover the usefulness of this suggestion
jcalz commentedon Apr 7, 2022
If the compiler automatically and silently augments the list of type parameters in some scope, don't you still have to "mentally keep track of another parameter" except now you have to deduce where it is?
In your proposal, isn't
Person<Judge>
an invalid type and you need to write something likePerson<Judge, "J. Reinhold">
in order for it to compile?Or if not, then what type is
Person<Judge>
? Is itPerson<Judge, string>
which would defeat the purpose of the type (sinceExclude<string, "">
is juststring
and points back to negated types as the actually desired feature here), or is it something else? And if so, what?RyanCavanaugh commentedon Apr 7, 2022
It seems like this is just trying to shoehorn in negated types, and has the same problems without solving any other use case. If a type parameter is anonymous then you can only refer to it once, which runs afoul of the golden rule of generics.
lirannl commentedon May 14, 2022
As far as I'm concerned, generally not. In the cases where I do have to keep track of it, it'll already be a regular, non-anonymous type parameter. As for deducing where it is - anonymous type parameters would only be usable in function parameters, or in interface/object type members, when you try to constrain a parameter to Person, you'll be prompted for both the Profession parameter, and a Name parameter - named after the property, and when you're prompted with name you'll go "Oh, I didn't declare that parameter! Must be an anonymous, let's use an anonymous" (which would effectively forward the parameter).
That is a good point. Yeah, it would be, but more importantly, having to specify the anonymous parameter every time would render the feature pointless. The only time this would be useful is if you pass an object literal/existing instance to a function accepting "Person", and even then in that function's signature, it's going to have to be specified as either another parameter, or as another anonymous parameter (so Person<Judge, >).
It might still be useful but I see how at that point at least I personally might as well just use negated types once they're implemented. I don't think it's completely useless but it's a big limitation and thanks for pointing that out.