Repository navigation
Suggestion: Generic Parameter Overloads #209
Description
Activity
Couple of questions that come out of thinking about this:
- Would a default value for type parameters in classes not accomplish the same thing? That is:
class View<TModel extends Backbone.Model = Backbone.Model> { ... } // currently typed as View, with this feature would be now typed as View<Backbone.Model> // because of the default type param value var v: View;
- The above leads to the question of whether you'd ever have a default type parameter value other than
anyor the constraint type. In that case, could the compiler just do this for you and instead of adding complexity to the language with a new piece of syntax it would do something like:
var v: View; // infers View<Backbone.Model> because View had a constraint var a: Array; // infers Array<any> because Array has no constraint on T
In the past we didn't want to infer
anyfor T which is why we required type parameters for generic types used in type positions. This is mitigated by using--noImplicitAnybut it's a little ugly to require that flag to make generics behave well for simple mistakes (ie forgetting to add a type parameter).Yes, both alternatives can do it. I like the second one even better that the compiler figures the type parameter for you. I think that would cover most cases, but just in case, combination of these two also seems to be viable.
One case I thought you don't want the compiler to automatically choose the default generic type for you is when you want to keep it to be generic, for example in
CollectionandViewOptionsbelow:class View<TModel extends Model> extends Events { constructor(options?: ViewOptions<TModel>); model: TModel; collection: Collection<TModel>; }
So it's still good to enforce
--noImplicitAnyhere.I dont want to be the "Me Too" guy but this is definitely needed for a signals library im using!
class Signal { } class Signal<T> { } class Signal<T,U> { } class Signal<T, U, V> { }👍
👍
Glad this is still being considered. This feature can be taken as two things:
- Generic Parameter Overloads, where same class name can get multiple definitions with different number of generic type parameters.
- Default value for generic type parameters, as Dan Quirk (@danquirk) said.
The first one can cover use cases for the second one, but the opposite is not true. If true C#-like Generic Parameter Overloads is supported where each overload defines its own implementation, then the compiler has to give a different name to each overload in the generated JS. For example,
View,View_$1,View_$2, etc. which can make it more complex. This feature is going to be nice, but I think allowing default value for generic type parameters is more needed, and would make zero footprint in the generated JS.Any movement on this one? I've run into the situation where I want to do this:
export interface Func<R> { (): R; } export interface Func<P, R> { (parameter: P): R; } export interface Func<P1, P2, R> { (parameter1: P1, parameter2: P2): R; } export interface Func<P1, P2, P3, R> { (parameter1: P1, parameter2: P2, parameter3: P3): R; } export interface Func<P1, P2, P3, P4, R> { (parameter1: P1, parameter2: P2, parameter3: P3, parameter4: P4): R; }But Instead I have to define them like this:
export interface Func0<R> { (): R; } export interface Func1<P, R> { (parameter: P): R; } export interface Func2<P1, P2, R> { (parameter1: P1, parameter2: P2): R; } export interface Func3<P1, P2, P3, R> { (parameter1: P1, parameter2: P2, parameter3: P3): R; } export interface Func4<P1, P2, P3, P4, R> { (parameter1: P1, parameter2: P2, parameter3: P3, parameter4: P4): R; }It's not the end of the world, but it is pretty annoying.
Reacted by Zarepheth, Justin Howard and Kim BeomSeoRon Penton (@RonPenton) We added generic defaults in #13487 for TS 2.3, so yes - we implemented the equivalent alternative to this.
lukas-zech-software commented
on Sep 12, 2017 More actionsWesley Wigham (@weswigham) I don't see how defaults are solving the problem with Ron Penton (@RonPenton)'s example
I tried this
export interface Func<R=void> { (): R; } export interface Func<R=void, P1=void> { (param1: P1): R; } export interface Func<R=void, P1=void, P2=void> { (param1: P1, param2: P2): R; } const fn1: Func = () => {}; const fn2: Func<string> = () => ''; const fn3: Func<string, number> = (param1: number) => ''; //error const fn4: Func<string, number, boolean> = (param1: number, param2: boolean) => ''; //error
but the compiler complains at
fn3withTS2322:Type '(param1: number) => string' is not assignable to type 'Func<string, number, void>'.and at
fn4withTS2322:Type '(param1: number, param2: boolean) => string' is not assignable to type 'Func<string, number, boolean>'.it seem like the compiler will always take the first overload of
Funcor am I doing something wrong here?
You should only need to have one interface declaration, for one thing:
export interface Func<R=void, P1=never, P2=never> { (param1: P1, param2: P2): R; } const fn1: Func = () => {}; const fn2: Func<string> = () => ''; const fn3: Func<string, number> = (param1) => ''; const fn4: Func<string, number, boolean> = (param1, param2) => '';
The rest are just adding more call signatures, which will get resolved using overload resolution. If you add them; you need to make sure the "most specific" ones are added first, otherwise what you described can happen (they're all being matched against
(): R, since it is ordered "first").Reacted by Lukas Zechlukas-zech-software commented
on Sep 12, 2017 More actionsThere are some drawbacks with that solution:
All function now must provide 2 parameters.export interface Func<R=void, P1=never, P2=never> { (param1: P1, param2: P2): R; } const fn1: Func = () => {}; fn1(); // TS2554:Expected 2 arguments, but got 0.
If I make all parameters optional, then there is no type safety in calling the methods with all expected parameters ....
export interface Func<R=void, P1=never, P2=never> { (param1?: P1, param2?: P2): R; } const fn1: Func = () => {}; const fn2: Func<string> = () => ''; const fn3: Func<string, number> = (param1) => ''; const fn4: Func<string, number, boolean> = (param1, param2) => ''; fn1(); // fine fn2(); // fine fn3(); // should raise error but does not fn4(); // should raise error but does not
Reacted by Ron Penton, Shuo Zheng and Dmitry PanyushkinWesley Wigham (@weswigham) I don't believe that solves the issue. I tried to use default generic parameters like Lukas Zech (@lukas-zech-software) did and ran into the same problems. The compiler expects parameters that don't exist.
Try out your code, you'll see that it doesn't work:
export interface Func<R=void, P1=never, P2=never> { (param1: P1, param2: P2): R; } const fn1: Func = () => {}; const fn2: Func<string> = () => ''; const fn3: Func<string, number> = (param1) => ''; const fn4: Func<string, number, boolean> = (param1, param2) => ''; fn1(); // Expected 2 arguments, but got 0. const x = fn2(); // Expected 2 arguments, but got 0. const y = fn3(123); // Expected 2 arguments, but got 1. const z = fn4(1, true); // This is the only one that works.I don't think default values work for the case I am interested in, either. I'm trying to get generic parameter binding working. Creators of a bindable would define the parameters that it accepts, and consumers would bind values for parameters through a "bind" call. Once all parameters are bound then the bind method is no longer available and the call method becomes available.
This looks something like this
interface IBindable<RequiredParams if-non-empty-object> { bind : <ParamSet>(p : ParamSet) => IBindable<RequiredParams less params in ParamSet>; } interface IBindable<RequiredParams if-empty-object> { call: () => Result; }In some ways this is very much like the classic C++ factorial template metaprogramming example
- locked and limited conversation to collaborators
on Jun 18, 2018
The suggestion is to allow generic parameter overloads like the following:
This is important because this would allow people to introduce generics in their library declarations without breaking large amounts of existing code for the consumers. We do this a lot in C#, and it is compatible with TypeScript design because it won't make any changes in the resulting javascript, and works pretty much like method overloads in TypeScript.
Related discussion: https://typescript.codeplex.com/discussions/452741