diff --git a/FAQ.md b/FAQ.md index 5d92bb9..a629ec2 100644 --- a/FAQ.md +++ b/FAQ.md @@ -797,7 +797,8 @@ Many questions in this FAQ boil down to "because types are erased". ### Why are function parameters bivariant? - > I wrote some code like this and expected an error: + > I wrote some code like this and expected an error:\ + > > ```ts > function trainDog(d: Dog) { ... } > function cloneAnimal(source: Animal, done: (result: Animal) => void): void { ... } @@ -849,6 +850,70 @@ so we have to take a correctness trade-off for the specific case of function arg ### Why are functions with fewer parameters assignable to functions that take more parameters? +> I wrote some code like this and expected an error: +> +> ```ts +> function handler(arg: string) { +> // .... +> } +> +> function doSomething(callback: (arg1: string, arg2: number) => void) { +> callback('hello', 42); +> } +> +> // Expected error because 'doSomething' wants a callback of +> // 2 parameters, but 'handler' only accepts 1 +> doSomething(handler); +> ``` + +This is the expected and desired behavior. +First, refer to the "substitutability" primer at the top of the FAQ -- `handler` is a valid argument for `callback` because it can safely ignore extra parameters. + +Second, let's consider another case: +```ts +let items = [1, 2, 3]; +items.forEach(arg => console.log(arg)); +``` + +This is isomorphic to the example that "wanted" an error. +At runtime, `forEach` invokes the given callback with three arguments (value, index, array), but most of the time the callback only uses one or two of the arguments. +This is a very common JavaScript pattern and it would be burdensome to have to explicitly declare unused parameters. + +> But `forEach` should just mark its parameters as optional! +> e.g. `forEach(callback: (element?: T, index?: number, array?: T[]))` + +This is *not* what an optional callback parameter means. +Function signatures are always read from the *caller's* perspective. +If `forEach` declared that its callback parameters were optional, the meaning of that is "`forEach` **might call the callback with 0 arguments**". + +The meaning of an optional callback parameter is *this*: +```ts +// Invoke the provided function with 0 or 1 argument +function maybeCallWithArg(callback: (x?: number) => void) { + if (Math.random() > 0.5) { + callback(); + } else { + callback(42); + } +} +``` + +`forEach` *always* provides all three arguments to its callback. +You don't have to check for the `index` argument to be `undefined` - it's always there; it's not optional. + +There is currently not a way in TypeScript to indicate that a callback parameter *must* be present. +Note that this sort of enforcement wouldn't ever directly fix a bug. +In other words, in a hypothetical world where `forEach` callbacks were required to accept a minimum of one argument, you'd have this code: + +```ts +[1, 2, 3].forEach(() => console.log("just counting")); + // ~~ Error, not enough arguments? +``` +which would be "fixed", but *not made any more correct*, by adding a parameter: +```ts +[1, 2, 3].forEach(x => console.log("just counting")); + // OK, but doesn't do anything different at all +``` ### Why are functions returning non-`void` assignable to function returning `void`?