-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Default values for generic parameters #49158
Comments
Not necessarily. If |
The feature you describe doesn't exist in C#. You can't have non-const default arguments for parameters, and C# doesn't have anything like union types or literal types. |
The situation you're describing is definitely something people run into (maybe I'll come back later and drop links here to Stack Overflow questions about it)... but it's not really feasible to have the compiler simply allow the default value like you're suggesting; type parameters on functions are specified by the caller, not the implementer, and nothing stops a confused/malicious/unhinged caller from manually specifying some other subtype of your constraint: class OtherChildClass extends ParentClass { prop = "hello"; }
foo<OtherChildClass>().prop.toUpperCase() // no compiler error, but explodes at runtime Right now there's no great way to deal with this. The options I see: Just assert that no caller is going to do the crazy thing, which means using that type assertion you don't like (aside: you're calling this "casting" but that ambiguous term often makes people think of runtime type coercion, so it's best avoided in TypeScript). You can also assign a default for the type parameter itself: function foo<T extends ParentClass = ChildClass>(bar: T = new ChildClass() as T): T { return bar; }
const cc = foo(); // const cc: ChildClass Or, you can actually prevent such calls by giving the function multiple overloaded call signatures that correspond to the desired behavior for when the function is called with or without an argument: function foo(): ChildClass;
function foo<T extends ParentClass>(bar: T): T;
function foo(bar: ParentClass = new ChildClass()) {
return bar;
}
const cc = foo(); // const cc: ChildClass You can even do both at once: function foo(): ChildClass;
function foo<T extends ParentClass>(bar: T): T;
function foo<T extends ParentClass>(bar: T = new ChildClass() as T) {
return bar;
}
const cc = foo(); // const cc: ChildClass This last version is as type safe and usable as we can get right now; it prevents callers from specifying a type parameter without a corresponding function argument, and the body of the function has It would be great if there were a convenient way to get the compiler to do this for us. I want to say "if the caller does not pass function foo<T extends ParentClass>(bar: T = new ChildClass() default T = ChildClass) {
return bar
} except with some better syntax devised by some smarter person. |
Yeah, this is really the crux of it: there's an impedance mismatch because generic inference is always based on the call site and whether the default value is used for a parameter is decided only after that, by which point |
Gotcha; thanks for all of the feedback. Also, my bad on the note about C# at the end - I could have sworn I was able to do that. I updated the original post to remove it. |
@Lakuna could you please reopen the issue ? I understand that the current version of typescript doesn't allow this, but I really think it should. |
No; this suggestion does not fit in TypeScript for the reasons described in the comments above. |
@Lakuna I am not talking about your suggestion, but more about having a way solve the use-case. I am implementing something like:
which for:
should return {1: { But I can't do it because of the nested stuff with the suggested solutions... |
I am also in favor of reopening, or maybe I'll create a new one that says "this is a problem people have, is there any way to improve it" rather than suggesting a particular approach. |
How i handled my case: type IErrorStatus = 500 | 501 | 503 | 507;
function RespondFail<THttpStatus extends IErrorStatus = 500>(httpStatus?: THttpStatus) {
const httpStatusOrDefault = httpStatus || 500;
return httpStatusOrDefault as THttpStatus,
} |
Suggestion
In TypeScript, there is not currently a way to supply a default value to a parameter with a generic type.
The following code throws an error:
Type 'number' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'number'.
However, it shouldn't be an issue ifT
is unrelated tonumber
as, in this case, we don't need to use any of its properties as anumber
.If we did need
T
to be related to the provided default value's type, we can already specify thatT
must extend a type. However, even in this case, an error is thrown:Type 'ChildClass' is not assignable to type 'T'. 'ChildClass' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'ParentClass'.
.In this example,
foo
should be able to useParentClass
or any subclass ofParentClass
(such asChildClass
), so aChildClass
should be an acceptable default (and is already an acceptable value to pass in).🔍 Search Terms
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Suggestion
Parameters with generic types should be able to accept default values as long as they fit within the specified scope of the generic type.
📃 Motivating Example
I am currently writing a math library that includes several classes of matrices. The
Matrix
class can represent any matrix, theSquareMatrix
class can only represent square matrices but has faster implementations of some methods because of it, et cetera. Each method has anout
parameter which allows the output of the method to be stored in an already existingMatrix
for performance purposes. Additionally, each method returns the matrix passed to theout
parameter in order to make chaining methods together easier.Since multiplying square matrices together is guaranteed to return a square matrix, the result of
SquareMatrix.prototype.multiply()
should be able to return aSquareMatrix
(and therefore take aSquareMatrix
for itsout
parameter). This would allow easy method chaining without ugly casting.This is how the situation above would currently be implemented:
This is how the situation above could be implemented if this change is made:
💻 Use Cases
My current use case is described in the motivating example section above, but this is a feature that I can see being useful in other cases as well. In the meantime, casting the parameter's default value to the generic type (
T
in all of the examples above) allows this to work; however, it shouldn't be necessary to perform a cast.The text was updated successfully, but these errors were encountered: