Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anonymous type parameters #48594

Open
lirannl opened this issue Apr 6, 2022 · 5 comments
Open

Anonymous type parameters #48594

lirannl opened this issue Apr 6, 2022 · 5 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@lirannl
Copy link

lirannl commented Apr 6, 2022

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?

@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Apr 6, 2022
@whzx5byb
Copy link

whzx5byb commented 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
Copy link
Author

lirannl commented 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
Copy link
Contributor

jcalz commented 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 like Person<Judge, "J. Reinhold"> in order for it to compile?

Or if not, then what type is Person<Judge>? Is it Person<Judge, string> which would defeat the purpose of the type (since Exclude<string, ""> is just string and points back to negated types as the actually desired feature here), or is it something else? And if so, what?

@RyanCavanaugh
Copy link
Member

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
Copy link
Author

lirannl commented May 14, 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?

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).

In your proposal, isn't Person<Judge> an invalid type and you need to write something like Person<Judge, "J. Reinhold"> in order for it to compile?

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants