-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
'Discarding Parameters' plus 'Duck Typing' makes every parameter optional? #9352
Comments
Your example here is specific to rest parameters, which get special treatment. Note that if you had written this with actually zero arguments, you'd get an error: class C1 { one() { } }
class C2 { one(hi: string) { hi.length; } }
var c1 = new C1();
var c2 = new C2();
c1 = c2; // Error The reason for this is that many callbacks provide a variable but predictable number of arguments. For example, var r = /(.*)_(.*)_(.*)/;
var x = 'a_b_c'.replace(r, (str, m1, m2, m3) => { ... }); |
Imagine
If a class method is defined with a single rest parameter, 0 arguments from a CallExpression is perfectly legal. Assigning something to that class method's interface which can accept no less than 1 argument from a CallExpression feels very different than most the static type safety we are usually provided from TypeScript compilation. Should methods defined directly on an object/class (maybe even something extra-restrictive/specific to the |
It's a design-trade off in terms of complexity/understandability vs soundness. The problem once you start special-casing these rules is that it's immediately unclear where to draw the line. For example, we believe this code to be OK, based on how it's used in the wild (concrete data point: this was illegal in one release of TypeScript and everyone complained about it or was confused) declare function invoke<T>(arr: T[], cb: (...args: T[]) => void);
invoke([1, 2, 3], (a, b, c) => a + b + c); Now let's say "Oh callbacks are special!". Then we write this code: declare function invoke<T>(arr: T[], cb: { success: (...args: T[]) => void; fail?: () => void });
invoke([1, 2, 3], { success: (a, b, c) => a + b + c }); Maybe that's OK and maybe it isn't? It's not obvious that this is a callback anymore, but from the developer's perspective it's a trivial refactoring. It also breaks the basic symmetry that if Now we write this code: declare class InvokeeBase<T> {
invoke(...args: T[]): void;
}
declare function invoke<T>(arr: T[], cb: InvokeeBase<T>);
class MyInvokee extends InvokeeBase<number> {
invoke(a: number, b: number, c: number) { ... }
}
invoke([1, 2, 3], new MyInvokee()); Again, it's a basic refactoring of the original code that we thought was totally fine. |
After reading the Comparing two functions section of the handbook I got myself pretty confused and concerned. I have an example below that I think (taken to the prepare-for-duck-typing-extreme) means I should code every class's method as if all of its parameters are optional (even though they're not defined as such).
TypeScript Version:
1.8.10 / next (1.9.0-dev.20160624-1.0)
Code
Actual behavior:
I have to remember to write run-time checking of
typeof hi === "string" ? hi.length : 0
instead of the simple/easyhi.length
inside myC2#one(hi: string)
method to prevent against a TypeError.Expected behavior:
I'd love for there to be some way for TypeScript to stop the
BAD assignment
line above; maybe one that turns off 'discarding' parameters altogether?The defense given for discarding parameters is a signature like
Array#forEach
-- where callers commonly omit the 2nd/3rd parameters to thecallbackfn
. That doesn't make sense to me though, why isn't the *.d.ts forArray#forEach
just defined as:Instead of how it is?
Maybe my targeting of parameter discarding is too strong, but it really seems like there should be some way to configure TypeScript to block the assertion of a
C2
instance to aC1
interface without requiring me to just write better code like:The text was updated successfully, but these errors were encountered: