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

[Feature] Generic Reference Syntax Proposal #59126

Closed
6 tasks done
MunMunMiao opened this issue Jul 3, 2024 · 8 comments
Closed
6 tasks done

[Feature] Generic Reference Syntax Proposal #59126

MunMunMiao opened this issue Jul 3, 2024 · 8 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@MunMunMiao
Copy link

MunMunMiao commented Jul 3, 2024

🔍 Search Terms

  • Generic
  • Generic Reference
  • Partial Type
  • Private Generic

✅ Viability Checklist

⭐ Suggestion

Background

The current version of TypeScript often requires explicitly passing multiple generic parameters, leading to code duplication and redundancy in certain scenarios. Inspired by TypeScript Issue #26242, we propose a new keyword ref, aimed at simplifying the reference and application of generics, and enhancing the readability and maintainability of the code.

Function Description

The keyword ref is used to declare a generic reference, which can be reused in functions, classes, or other generic definitions without the need for repeated declaration. Using ref, one can define default values for generics, and it supports exporting and cross-file referencing.

Syntax
// Declaring a reference
// If no default value is set, it is inferred from the context.
ref T;

// Declaring a reference and setting a default value
ref T = string;

// The declaration reference can be constrained to certain types.
ref T extends string;

// The declaration of a reference can be constrained to certain types and a default value can be set.
ref T extends string = 'Hello';

// When defining a reference, if a value is set,
// then the reference will be constrained to the type of value.
//
// T type is: T extends 'Hello'
ref T = 'Hello';

// Exporting a reference
export ref T;

// ❌ The variable cannot be reassigned after being defined.
T = number;

📃 Motivating Example

Solving the Optional Generic Input Problem

Use ref to simplify function definitions, automatically infer generic parameters, and achieve private generic reference functionality:

function define<T, Agrs extends O[]>(value: T, ...agrs: Agrs): Ref<Agrs> {}

// If you need to manually limit the value to be a string
// you not only need to set the first generic but also input the second generic
// which cannot be automatically inferred.
define<string>('Hello', 1, 'A', true) // 👎 Error: Expected 2 type arguments, but got 1.


// when you't want the user pass O or only want the to pass type of
// you can use the ref generic reference.
ref O
function define<T>(value: T, options: O): Ref<O> {}
define<string>('input any strings', 123) // 👍 <string>(value: string, options: number) => Ref<number>

// When you need to allow users to fill in the type
function define<T, O>(value: T, options: O): Ref<O> {}
define<string, string>('input any strings', 'i am here') // 👍 <string, string>(value: string, options: string) => Ref<string>
Cross-Function, Cross-File Constraints

ref supports cross-file referencing, achieving unified type constraints:

// option.ts
export ref Tref;

export function define(value: Tref): Ref<Tref> {}

// example.ts
import { type Tref } from 'option.ts';

export function example(value: Tref): Ref<Tref> {}

Conclusion

This proposal aims to simplify the use of generics by introducing the ref keyword, thereby enhancing TypeScript's development efficiency and code quality.

For instance, Vue.js V2 v3-component-options.d.ts#L75 If there is a ref keyword, then won't be so many generic variables stored on the function, and the code will be much cleaner.

We look forward to community feedback and further discussion.

❗️Warning❗️

This proposal does not conflict with optional input generics, but it can solve problems such as private generics.

@MunMunMiao MunMunMiao changed the title TypeScript Generic Reference Proposal [Feature] Generic Reference Proposal Jul 3, 2024
@jcalz
Copy link
Contributor

jcalz commented Jul 3, 2024

I don't understand the scoping for these ref type parameters. What does it mean for both define and example to have access to the same Tref? It almost feels like #31894. How does this differ from that?

For #26242 I think it would be almost required for the type parameter to be scoped to the function and not some outer scope. What are the benefits of ref U; declare const f: <T>(u: U) => F<T, U> compared to something like declare const f: <T, U=infer>(u: U) => F<T, U>?

@MunMunMiao
Copy link
Author

MunMunMiao commented Jul 3, 2024

The purpose is to address the issue of privatized generics and optional generics You can refer to my example 1.

(aside, you've got the [] in the wrong place in your first example)

I don't understand the scoping for these ref type parameters. What does it mean for both define and example to have access to the same Tref? It almost feels like #31894. How does this differ from that?

For #26242 I think it would be almost required for the type parameter to be scoped to the function and not some outer scope. What are the benefits of ref U; declare const f: <T>(u: U) => F<T, U> compared to something like declare const f: <T, U=infer>(u: U) => F<T, U>?

When we need to infer the return value a function, we need to infer it the function input values. This process does not require user input. However, the existing design approach requires that the generic variable be defined on the function, the user to interfere with our inference.

function define<T, Options extends Option[]>(value: T, ...agrs: Options): Ref<Options> {}
// In this case
// we do not want the user to set a second generic because it will interfere with our inference of args, 
// but we need the user to fill in the first generic to constrain the value.
define<string, any>('')
// So I need to hide my `Options[]`
ref Options
function define<T>(value: T, ...args: Options): Ref<Options> {}

define<string>('') // This is great.

@MunMunMiao MunMunMiao changed the title [Feature] Generic Reference Proposal [Feature] Generic Reference Syntax Proposal Jul 3, 2024
@jcalz
Copy link
Contributor

jcalz commented Jul 3, 2024

I still don't understand what it means for two different functions to access the same ref.

I also don't quite see the use case for preventing a user from manually specifying the type argument. You say "interfering with our inference" but why would that be a problem? What is the use case for private generics? Could you show an example where a user "interfering" with inference leads to a bad outcome?

@MunMunMiao
Copy link
Author

MunMunMiao commented Jul 3, 2024

I still don't understand what it means for two different functions to access the same ref.

For example, the code of Vue. Link Can be used in overloaded or more functions of the same type (this requires more examples to be given by everybody, and the use of the same ref for multiple functions is just one possibility)

I also don't quite see the use case for preventing a user from manually specifying the type argument. You say "interfering with our inference" but why would that be a problem? What is the use case for private generics? Could you show an example where a user "interfering" with inference leads to a bad outcome?

you can see this Playground

this's use ref syntax Playground

@RyanCavanaugh
Copy link
Member

This design doesn't really seem to fit in with the language very well, primarily because the scoping is extremely confusing. You have this example:

ref O
function define<T>(value: T, options: O): Ref<O> {}
define<string>('input any strings', 123) // 👍 <string>(value: string, options: number) => Ref<number>

But you could equally have written something like

ref O
function define<T>(value: T, options: O): Ref<O> {
  // is p: number because it's a new inference site for O
  // or is p: O? How do you tell?
  const p = inner(32);
  function inner(foo: O): O { }
}

It's not at all clear whether inner gets a "fresh" copy of O that does its own round of inference or not, or how you would opt into the opposite behavior if the default wasn't the one you wanted (are empty type parameter lists now "a thing" ?).

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Declined The issue was declined as something which matches the TypeScript vision labels Jul 11, 2024
@MunMunMiao
Copy link
Author

MunMunMiao commented Jul 12, 2024

@RyanCavanaugh

ref O
function define<T>(value: T, options: O): Ref<O> {
  // is p: number because it's a new inference site for O
  // or is p: O? How do you tell?
  const p = inner(32);
  function inner(foo: O): O { }
}

👍Great! O is a number because its type can be determined in context.

The purpose of this is to address the issue:
1、Some types need automatic reasoning, but the caller does not need to declare it on the function, for example: <T>(v:T) => T => ref T; (v: T) => T
2、Implement support for optional generics.
3、Private generic support.

Because the need for optional generics in the community has been raised for a time, I hope that the TypeScript official can come up with a solution to address the dilemma of us developers.

@MunMunMiao
Copy link
Author

@RyanCavanaugh

In addition, if 'ref' is used in multiple places, the compiler should infer the collected type:

ref O

const func: (value: O) => O = v => v 

func(1)  // <- collect: number
func('1')  // <- collect: string
func(true)  // <- collect: boolean
func(new Date())  // <- collect: Date

The compiler will collect: number and string and boolean and Date

If the collected by the compiler are inconsistent, an error will be reported, otherwise, it will compile normally.

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jul 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants