Reputation: 1013
I'm trying to define some generic function types in Typescript. It seems like there are two similar ways to do it, and they both kind-of work. However the second form - see below ConcatY
- doesn't seem as flexible, or at least I don't know how to indicate that a function takes one of these with specific argument types. Is there a way to define a function that takes a ConcatY
for numbers? In general, how should I think about the differences between these two ways to define a generic function?
// Two similar looking function types
type ConcatX<T> = (a: T, b: T) => T;
type ConcatY = <T>(a: T, b: T) => T;
// Can create instances of each of these types
const sum: ConcatX<number> = (a, b) => a + b;
const product: ConcatY = (a: number, b: number) => a + b;
// Can define a function that takes a ConcatX
function DoMathX(sum: ConcatX<number>) {
console.log(`1 + 1 is ${sum(1, 1)}`);
}
// But can't define a function that takes a ConcatY for numbers
// A and B are "unknown"
// Or is there a way?
function DoMathTwo(sum: ConcatY) {}
Upvotes: 9
Views: 8474
Reputation: 187014
type ConcatX<T> = (a: T, b: T) => T;
Is a generic type alias, which contains a function that uses the generic parameter. The generic parameter this function uses is locked in once the type is resolved. That can be handy when some type needs to set the type of your function.
For instance here:
const sum: ConcatX<number> = (a, b) => a + b;
This says that, externally to this function, you declare that the arguments of this function are numbers.
Note that this is not really part of the function type at all. This approach isn't fundamentally different than something like:
type ContatX<T> = { sum(a: T, b: T): T, someValue: T }
The point being that the T
is set outside the function entirely, and the function just picks that up to use.
type ConcatY = <T>(a: T, b: T) => T;
Is a generic function. The generic parameter is set when the function is called, and can be different each time it's called. And that parameter may be inferred from the type of the arguments a
and b
.
To type guard against bad usage here you would instead check the return type:
const resultStr: string = sum(1,2) // error: cannot assign number to string
const resultNum: number = sum('a','b') // error: cannot assign string to number
In both cases the function call is completely valid, but the result is not what the type system is expecting, so you get an error.
If you want a specific subtype of concat where the arguments must be numbers, then you want the generic type ConcatX
.
But if your function could take lots of different arguments, and the return type depends on the type of those arguments, and you don't know exactly what types it will be called with ahead of time, then you want the generic function ContactY
.
Which all means that if you want to:
"to indicate that a function takes one of these with specific argument types"
Then you need to use ContactX
with a generic parameter to create a function type with the T
locked to whatever you want.
Upvotes: 11