In JavaScript and TypeScript, functions are generic, which means a:
type f = (...args: [number]) => unknown
// aka: (foo: number) => unknown
is automatically a
type f = (...args: [number, ...any[]]) => unknown
Reasonable. If a function uses only the first few arguments, it is no harm to provide more.
And here come "optional parameters" in TypeScript. No worry in JavaScript since there are no "non-optional parameters":
type g = (foo: number, bar?: number) => unknown
It is also a:
(foo: number) => unknown
Why not? the second parameter is optional, it can be used like that.
So now, a g
is also an f
.
But wait, remember we have the second form of f
:
const H = (h: (foo: number, bar: string) => void) => {
h(0, '')
}
const F = (f: (foo: number) => void) => {
H(f)
}
const g = (foo: number, bar?: number) => {
console.log(bar ?? 0 + foo + 1)
}
F(g)
TypeScript would gladly accept these code even in its most strict type checks, including strictFunctionTypes
: a g
is an f
, we already know that, and an f
is an h
, we know that too. But is a g
also an h
?
That is the question.
We have been using a lot of functional APIs. Array.prototype.map
for example, accepts an executor (element, index?, array?) => any
, which is practically an element => any
.
But if the executor is from somewhere else in the later form, the "g
is not h
" can be a problem, a problem TypeScript unable to detect:
class Foo<T> {
private foo: T[]
...
function bar<U>(f: T => U) {
return this.foo.map(f)
}
...
}
Let's imagine what could happen here.
Top comments (0)