-
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
Typed ...rest parameters with generics #1024
Comments
I think you will be able to do a similar thing with tuple types. function myFunction<T extends any[]>(argary: T) {
}
myFunction<[string, number]>(["a", 1]); |
That really looks like what I will need, thanks! Do you know when 1.3 will be released and if the current 1.3 is stable enough to use in production? |
1.3 is pretty stable; I'd give it a shot. We're using it internally, at least. Feel free to drop a comment here and I'll re-open this if tuple types turn out to not be what you're looking for and you want to discuss further. Thanks! |
I played with this now, but I couldn't achieve what I really would like to do. The example from above works: but I don't want to pass an array to myFunction, only the elements of the array like this: I tried to achieve that like this:
The problem is, the compiler is giving me this error: I don't understand why T is not an array type in this case? |
The spec says
We should consider whether it is permissible to type a rest parameter with a bare type parameter in certain circumstances (@ahejlsberg), though as I state below, I'd need to see a use case that isn't subsumed by union types. What are you trying to do exactly? I'm curious about the utility of rest parameters typed as tuple types; I can't imagine a function implementation doing anything with it other than use the union type of the arguments, in which case you'll be able to just use the following in TypeScript 1.4: function bahHumbug<T>(...args: T[])
{
}
bahHumbug<string|number>("a", 1); |
Thanks for the reply! The reason I don't understand that error message is that T extends any[] so it's an array type (?) and also if I write any[] instead of T it compiles. // aka "Subject" in Observer pattern
class Dispatcher<T extends any[]>
{
public addListener(listener:(...args:T)=>any)
{
// ...
}
public dispatch(...args:T) {
// ...
}
}
var dispatcher:Dispatcher<[string, number]> = new Dispatcher();
// should work:
dispatcher.addListener(myGoodListener);
// should throw compile error:
dispatcher.addListener(myBadListener);
// should work:
dispatcher.dispatch("hey", 1);
// should throw compile error:
dispatcher.dispatch(1);
function myGoodListener(p1:string, p2:number)
{
// ...
}
function myBadListener(p1:number)
{
// ...
} This now gives the previously mentioned error: "A rest parameter must be of an array type". Another approach that works is if I restrict to use just one parameter to dispatch, with that everything works, but the absolute best would be if I could allow the users of this class to use it with any number of parameters but all of them being type-safe. |
I think we've discussed something like this, but It's something to consider a bit more. Given that it's only 2 extra characters to dispatch/notify, and that listeners address their elements in the same manner, this shouldn't be an awful workaround in the mean time. As a separate matter, the downlevel emit for rest parameters is probably less efficient than using a tuple anyhow, given that optimizers have a difficult time when using |
What do you refer to as those 2 extra characters? You mean to dispatch with a single array like this as suggested by SaschaNaz ?
Or which workaround were you referring to? |
Yes, the extra two characters are class Dispatcher<T>
{
public addListener(listener: (args: T) => void): void
{
// ...
}
public dispatch(args: T): void {
// ...
}
}
var dispatcher = new Dispatcher<[string, number]>();
function myGoodListener(args: [string, number])
{
// ...
}
// Note: this one will work when we have destructuring
/*
function anotherGoodListener([p1, p2]: [string, number])
{
var myStr = "Hello " + p1;
var myNum = p2 + 1;
}
*/
dispatcher.addListener(myGoodListener);
// dispatcher.addListener(anotherGoodListener);
dispatcher.dispatch(["hey", 1]); |
Without additional features the closest you can get is to define family of Dispatcher interfaces that follow this pattern: interface Dispatcher1<T0> {
addListener(listener: (p0: T0) => void): void;
dispatch(p0: T0): void;
}
interface Dispatcher2<T0, T1> {
addListener(listener: (p0: T0, p1: T1) => void): void;
dispatch(p0: T0, p1: T1): void;
}
interface Dispatcher3<T0, T1, T2> {
addListener(listener: (p0: T0, p1: T1, p2: T2) => void): void;
dispatch(p0: T0, p1: T1, p2: T2): void;
}
// ... up to some meaningful limit and then have a general purpose implementation with an overloaded factory method (or something similar to that effect): class Dispatcher {
public addListener(listener: (...args: any[]) => void): void {
// ...
}
public dispatch(...args: any[]): void {
// ...
}
public static create<T0>(): Dispatcher1<T0>;
public static create<T0, T1>(): Dispatcher2<T0, T1>;
public static create<T0, T1, T2>(): Dispatcher3<T0, T1, T2>;
public static create(): Dispatcher {
return new Dispatcher();
}
} |
@DanielRosenwasser thanks, yes that could work. One reason I wanted to avoid that approach is that it may be wrong from a performance point of view to create a new array at each dispatch call (although in most cases it probably doesn't matter), and you have a good point about arguments not being efficient for optimizers. @ahejlsberg thanks, that is quite close to what I was looking for, I don't think I would need more than a few parameters anyway. (Btw thank you all for your work on TS, it's a really huge step to enhance JS in my opinion). |
@RyanCavanaugh @DanielRosenwasser, I believe I've found a situation in which a tuple doesn't cut the mustard. In this case, I'm trying to extend the types for a third-party library This lib extends the redux reducer to return both the state object and a I'd like to extend their types for Cmd.run, which allows a user to specify a function to be run, along with args to be passed to that function, and "action creators" to be called on success or failure: Cmd.run(fetchUser, {
successActionCreator: userFetchSuccessfulAction,
failActionCreator: userFetchFailedAction,
args: ['123']
}) Currently, there is no type safety between the arguments of the function to be run and the value of the The current interface for static readonly run: <A extends Action>(
f: Function,
options?: {
args?: any[];
failActionCreator?: ActionCreator<A>;
successActionCreator?: ActionCreator<A>;
forceSync?: boolean;
}
) => RunCmd<A>; I'd like to do something that enforces a match between inputs and outputs of static readonly run: <A extends Action, Args, Result>(
f: (...args: Args) => Result,
options?: {
args?: Args;
failActionCreator?: () => A;
successActionCreator?: (result?: Result) => A;
forceSync?: boolean;
}
) => RunCmd<A>; However, trying this, I get the error that brought me to this issue:
Is there a way I can accomplish this type safety, without altering Additionally, it would be swell if I could use |
Digging further, I ran across a proposal for Variadic Kinds which directly addresses this issue. It looks like I may have to hold out hope for that. But if anyone has clever workarounds in the meantime, I wouldn't complain. |
Is it possible to add type info to the rest parameters in a way that each individual parameter can have a different type?
This works, but with this there can be any number parameters, and all have to have the same type:
It would be really useful if I could force the exact number of arguments and the types for each one, but all this within generics, so something like this (but this is obviously syntactically wrong):
An example use case is writing an observer class that the user could specify the arguments of the listener functions for:
If this is not supported, what do you recommend using instead? I can use any[] of course, or living with the constraint of having a fixed number of fixed typed parameters.
The text was updated successfully, but these errors were encountered: