I am new to TS and am learning it by reading Programming Typescript book by O Reilly. In the book, the author is implementing his own version of JS's built-in call
function.
function call<T extends unknown[], R>(
f: (...args: T) => R,
...args: T
): R {
return f(...args)
}
function fill(length: number, value: string): string[] {
return Array.from({length}, () => value)
}
call(fill, 10, 'a') // evaluates to an array of 10 'a's
One thing I am really struggling with is why is unknown
used here? Why can't we do it like this?
function call<T, R>(
f: (...args: T[]) => R,
...args: T[]
): R {
return f(...args)
}
Of course, this version errors out but why?
Thanks
CodePudding user response:
Because we might have better information.
function call<T, R>(
f: (...args: T[]) => R,
...args: T[]
): R
This demands that we have some type T
for which the array takes zero or more T
(in the form of an array) as arguments. If we have a function that takes a variable number of strings, that works great.
However, there are other types that extend unknown[]
that aren't, in as many words, arrays. Namely, those are tuple types. If we want to call this with a function
function foobar(x: string, y: number)
Then your version of the function would require that T
be string | number
, which means I could call your function with [0, "A"]
, or [0, 0, 0, 0, 0]
, or []
, or any number of other incorrect call signatures. However, the other function you proposed,
function call<T extends unknown[], R>(
f: (...args: T) => R,
...args: T
): R {
return f(...args)
}
This will take T
to be [string, number]
. Every instance of [string, number]
can be assigned to a variable of type unknown[]
, so the former is compatible with the latter. And now we can only call this function with a list whose size is known statically to be 2 and whose first element is a string
and the second is a number
.
CodePudding user response:
By writing T extends unknown[]
you restrain T
to have all the properties of an unknown[]
. Two consequences:
- The caller of the
call
function can't use it with aT
that wouldn't satisfy this constraint - In the implementation, you can use a variable of type
T
like anunknown[]
even if the actual type is not known (only the caller knows it)