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
It would be really useful if I could set default values for my generic type variables. In many cases, the users of my class don't want to use a different value and so it's cumbersome to having to type it in.
then the people who don't care about the type of userData (because they don't use it) and the people who are fine with it being a String don't have to set a generic type but others can if they want.
Also, these type variables could default to any or the type they extend (if they do) without explicitly defaulting them.
For example: <T extends MyClass> would default to MyClass (without explicitly writing it)
and <T> could default to any
While that could be useful in some cases, I feel I wouldn't use that for these reasons:
it's cumbersome to write new classes just for this
it's inconvenient for the users of my class, ideally they shouldn't have to know that there is a different class written just for the case of having some generic types default to something.
In François de Campredon (@fdecampredon) 's example code with 3 generic variables it would be especially hard to cover all the use cases (some users might want to use just P, others just S, others just C, others P and S, etc..
Reacted by Boris Cherny, Ethan Resnick, Slavo Vojacek, ruslan-simonenko and Kaelin Laundry
I'm working on a library right now that could have really benefited from this. When combined with constraints, I'd expect the syntax to look something like the following:
class Component<P extends IProperties = Properties, S = {}, C extends IContext = Context> {
props: P;
state: S;
context: C;
....
}
Btw, why is this labeled under "Needs Proposal" ? This is already present in C++, not in C# unfortunately. However I don't think this would be hard to implement?
for all cases where a generic function / class is used (called / instantiated), and a generic type variable that has a default value is not explicitly set, that default value is injected.
So in the above example, because f is called without given an explicit type it will default to the default generic argument (string) and that's it.
If you change "string" to "number" in the above snippet it will be a compile error because "s" is of type number.
Do you see any problems with this approach? Is it that with a default value we ignore the possibility of type inference that would otherwise work just by looking at the types of the passed function parameters (without setting a generic type)?
Some options are:
ignore type inference when there's a default generic argument (the naive idea)
if type inference would allow to to infer a type, use that type, otherwise use the default type
use the inferred type only if it's "stronger" than the default type (meaning if the inferred type is a subtype of the default type), otherwise use the default type
My suggestion would be that type defaulting happens in place of an any type or {} type inference; that is, to expand on Ryan Cavanaugh (@RyanCavanaugh)'s above example:
declare function f<T = string>(a: T, x: (n: T) => void): void;
// 'T' is a string here, because the default overrides the inference of any
f(null, s => { });
// 'T' is a number here due to existing inference
f(15, s => { });
I find it more important on classes, however, where type inference may not be able to be performed.
class MyClass<T = string, U = IMyInterface>
{
constructor(a: T) { }
}
// 'T' is a number, because it was inferred.
// 'U' is 'IMyInterface', because it was inferred as '{}' by existing logic.
var myVar = new MyClass(15);
// 'T' is a string, because it was inferred as 'any' by existing logic.
// 'U' is 'IMyInterface', because it was inferred as '{}' by existing logic.
var myVar2 = new MyClass(null);
// 'T' is a number, because it was specified.
// 'U' is 'IMyInterface', because it was inferred as '{}'
var myVar3 = new MyClass<number>(null);
// 'T' is a number, because it was specified.
// 'U' is 'string', because it was specified'
var myVar4 = new MyClass<number, string>(null);
As indicated in my example, I'd propose that, just like default (non-type) parameters in function calls, all type parameters after the first defaulted type parameter must have a default.
I'd also suggest that type constraints are evaluated after all inference is complete, in case it matters for constraint checking. (I can't imagine a situation where it would, given type constraints cannot reference type parameters in the same type parameter list at this time.)
I'd rather not discuss named type parameters, as that could be a separate feature entirely. (Open a separate issue, perhaps, Tingan Ho (@tinganho)?)
Matt DeKrey (@mdekrey) I agree, it would work well if the default type was only relevant for the {} case.
Is there a use case for specifying both a default and extends? My suggestion would be to not support specifying both, since it might complicate type inference rules.
I suspect that the correct behavior is to defer an application of default generic parameters till the moment when exact count of generic parameters will be determined from the context:
if the context asks less generic parameters -- then leave asked generic parameters open (ignoring possible defaults) and instantiate the rest with the defaults (or report an error if there are no default types specified).
In that case if we have an interface that accepts an unary type constructor
interfaceX<T<~>>{value: T<number>}
and a binary type constructor with two defaults
interfaceY<A=string,B=boolean>{a: A,b: B}
then the application should silently convert binary Y<A,B> to the unary Y<A> = {a:A, b: boolean} by applying the trailing default parameters:
typeXY=X<Y>;/* should be { value: { a: number, // due to X b: boolean // Y's second default parameter }} */
And to clarify, the query signature for my two examples would be this:
// Using the default parameterinterfaceKeyDatabaseextendsDatabase<KeyPromise>{query<T>(s:string,args: any[]): KeyPromise<T>;}// Using a specialization (note the unused `T`)typeNumberPromise<T>=KeyPromise<number>;interfaceNumberDatabaseextendsDatabase<NumberPromise>{query<T>(s:string,args: any[]): KeyPromise<number>;// i.e. NumberPromise<T>}
Generic parameter defaults will make the implementation of #1213 and #9949 considerably harder. They seem challenging at presents, so it doesn't help.
Defaults are a source of ambiguities as well. Adding a default may make a function backward incompatible. E.g.
declarefunctionc<A>(f: (x: A)=>A,g: (a: A)=>A): A;declarefunctionf<T=number>(x: T): T;declarefunctiong<T=string>(x: T): T;c(f,g)// A = string / number or T?
Better type inference and type aliases are more obvious and consistent solutions since defaults help only in the case envisioned by the library/typings author and are opaque to the code maintainers.
The only true benefit of defaults is generifying existing typings (as pointed out by Blake Embrey (@blakeembrey)'s example) but I'd much rather see real generics first..
The only true benefit of defaults is generifying existing typings (as pointed out by Blake Embrey (@blakeembrey)'s example) but I'd much rather see real generics first.
If nested types make it, then this would all of a sudden become far more interesting. You could make a React type namespace, so you could properly type the library, including raw element references, and ensure that React Native-specific components can't be erroneously used in DOM-specific or renderer-independent components. That being simply by using a generic React type instead. It's an extra layer of verification that would allow much more thorough typing.
declarefunctionc<A>(f: (x: A)=>A,g: (a: A)=>A): A;declarefunctionf<T=number>(x: T): T;declarefunctiong<T=string>(x: T): T;c(f,g)// A = string / number or T?
Ryan Cavanaugh (@RyanCavanaugh) Are you guys planning to add a milestone to this guy? Looks like you had the spec (and implementation?) mostly fleshed out.
Boris Cherny (@bcherny) work in progress at #13487. I would expect this to be in 2.2 but may slip to 2.3 if we need a few more revisions on the PR.
Reacted by Alexandre Galays, Braden Snell, AbraaoAlves, Morten Christensen, Dariusz Filipiak and Erik Cupal
Reacted by Claudia Meadows, Nicholas Guyett, Alexandre Galays, Luca Trazzi, Tyler Brinkley, Pavel Birukov , rapgolya, bykbtzr, Matt DeKrey, Misha Kaletsky and 8 more
Activity
[-]Default template parameters[/-][+]Default generic type variables[/+]fdecampredon commentedon Mar 2, 2015
👍 It would greatly simplify definition for some lib, for example something like react components would ideally be :
but most of the time
SandCare not used, with something like :It would be easy to create simple component that don't use those generic types:
DanielRosenwasser commentedon Mar 2, 2015
Interesting idea, though it would seem that this functionality is easily achieved through a subclass.
Are there any use cases where doing this becomes annoyingly difficult, or where it doesn't quite achieve the same thing?
andrewvarga commentedon Mar 3, 2015
While that could be useful in some cases, I feel I wouldn't use that for these reasons:
In François de Campredon (@fdecampredon) 's example code with 3 generic variables it would be especially hard to cover all the use cases (some users might want to use just P, others just S, others just C, others P and S, etc..
mdekrey commentedon Apr 1, 2015
I'm working on a library right now that could have really benefited from this. When combined with constraints, I'd expect the syntax to look something like the following:
poeschko commentedon Jun 26, 2015
+1. This would be great indeed when working with React.
andrewvarga commentedon Aug 5, 2015
Btw, why is this labeled under "Needs Proposal" ? This is already present in C++, not in C# unfortunately. However I don't think this would be hard to implement?
RyanCavanaugh commentedon Aug 5, 2015
Someone needs to write down what all the rules for this would be.
For example, would defaults be legal in all generic type parameter positions? If so, what's the behavior in function calls?
tinganho commentedon Aug 5, 2015
In addition to default type parameter, it would also be great to have named type argument. So I don't need to care about the order.
andrewvarga commentedon Aug 5, 2015
Ryan Cavanaugh (@RyanCavanaugh) my first (maybe naive) idea for this was a simple preprocessor which would work like this:
So in the above example, because f is called without given an explicit type it will default to the default generic argument (string) and that's it.
If you change "string" to "number" in the above snippet it will be a compile error because "s" is of type number.
Do you see any problems with this approach? Is it that with a default value we ignore the possibility of type inference that would otherwise work just by looking at the types of the passed function parameters (without setting a generic type)?
Some options are:
Let me know if I'm missing something!
andrewvarga commentedon Aug 5, 2015
Tingan Ho (@tinganho) that sounds like a good idea!
mdekrey commentedon Aug 6, 2015
My suggestion would be that type defaulting happens in place of an
anytype or{}type inference; that is, to expand on Ryan Cavanaugh (@RyanCavanaugh)'s above example:I find it more important on classes, however, where type inference may not be able to be performed.
As indicated in my example, I'd propose that, just like default (non-type) parameters in function calls, all type parameters after the first defaulted type parameter must have a default.
I'd also suggest that type constraints are evaluated after all inference is complete, in case it matters for constraint checking. (I can't imagine a situation where it would, given type constraints cannot reference type parameters in the same type parameter list at this time.)
I'd rather not discuss named type parameters, as that could be a separate feature entirely. (Open a separate issue, perhaps, Tingan Ho (@tinganho)?)
Andael commentedon Aug 8, 2015
Matt DeKrey (@mdekrey) I agree, it would work well if the default type was only relevant for the
{}case.Is there a use case for specifying both a default and
extends? My suggestion would be to not support specifying both, since it might complicate type inference rules.47 remaining items
Artazor commentedon Jan 9, 2017
I suspect that the correct behavior is to defer an application of default generic parameters till the moment when exact count of generic parameters will be determined from the context:
In that case if we have an interface that accepts an unary type constructor
and a binary type constructor with two defaults
then the application should silently convert binary
Y<A,B>to the unaryY<A> = {a:A, b: boolean}by applying the trailing default parameters:dead-claudia commentedon Jan 9, 2017
Anatoly Ressin (@Artazor) That was my original intuition with my idea.
And to clarify, the
querysignature for my two examples would be this:gcnew commentedon Jan 23, 2017
Generic parameter defaults will make the implementation of #1213 and #9949 considerably harder. They seem challenging at presents, so it doesn't help.
Defaults are a source of ambiguities as well. Adding a default may make a function backward incompatible. E.g.
Better type inference and type aliases are more obvious and consistent solutions since defaults help only in the case envisioned by the library/typings author and are opaque to the code maintainers.
The only true benefit of defaults is generifying existing typings (as pointed out by Blake Embrey (@blakeembrey)'s example) but I'd much rather see real generics first..
dead-claudia commentedon Jan 25, 2017
Marin Marinov (@gcnew)
If nested types make it, then this would all of a sudden become far more interesting. You could make a React type namespace, so you could properly type the library, including raw element references, and ensure that React Native-specific components can't be erroneously used in DOM-specific or renderer-independent components. That being simply by using a generic React type instead. It's an extra layer of verification that would allow much more thorough typing.
That would be a good question, though.
bcherny commentedon Feb 8, 2017
Ryan Cavanaugh (@RyanCavanaugh) Are you guys planning to add a milestone to this guy? Looks like you had the spec (and implementation?) mostly fleshed out.
RyanCavanaugh commentedon Feb 8, 2017
Boris Cherny (@bcherny) work in progress at #13487. I would expect this to be in 2.2 but may slip to 2.3 if we need a few more revisions on the PR.
bcherny commentedon Feb 8, 2017
Awesome, thanks for the excellent work Ryan Cavanaugh (@RyanCavanaugh)!
iamssen commentedon Feb 16, 2017
👍 Thanks. I've been wating a long time.