if I have a generic function:
const f1 = <T>(x: T) => console.log(x)
I can define a specialized version for f1:
const f2 = (x: number) => f1(x)
then typescript will throw an error if I do this:
f2('6')
but f2 is actually another function that calls f1.
Is there a way to define f2 as a specialized version of f1? something like:
const f2 = f1<number> // this doesn't work
You can define the generic type before like that
type Fn<T = any> = (x: T) => void
const f1: Fn = (x) => console.log(x)
const f2: Fn<number> = f1
f2('6') // Argument of type '"6"' is not assignable to parameter of type 'number'.
Here is a link to a working playground
I'm going to change your example function because the type <T>(x: T)=>void
is not doing anything useful with the generic type parameter, and is practically the same as (x: unknown)=>void
, a concrete function type. Instead, let's look at a function of type <T>(x: T)=>T
, which preserves the input type and outputs a value of the same type, where (x: unknown)=>unknown
would not suffice:
const f1 = <T>(x: T) => (console.log(x), x);
// const f1: <T>(x: T) => T
const n: number = 6;
const sixNumber = f1(n); // const sixNumber: number
const s: string = "6";
const sixString = f1(s); // const sixString: string
What you want to do is take the generic function f1
and use it as if it were a concrete function f2
of type (x: number)=>number
. This is a widening of the the type of f1
, since the every function of type <T>(x: T)=>T
is also a function of type (x: number)=>number
but not vice versa. The compiler recognizes this as a valid widening and you can just annotate f2
as the widened type yourself:
const f2: (x: number) => number = f1; // no error
const sixNumberOkay = f2(n); // okay
const sixStringNotOkay = f2(s); // error! string is not number
So that's good.
--
Now, as you noted, what you can't do is take the type of f1
and the type number
and automatically produce the type of f2
. The type system has no way for you to represent substituting types for type parameters. Neither f1<number>
nor typeof f1<number>
are valid. TypeScript doesn't have enough support for higher kinded types to express this, at least not purely at the type level .
In TypeScript 3.4, TypeScript introduced improved support for inferring generic functions from other generic functions . So while you can't do the type manipulation you're asking about at the type level alone, you can write some functions that make the compiler do that manipulation anyway. For example:
function specify<A extends any[], R>(f: (...a: A) => R) {
return () => f;
}
The specify()
function takes any function, even a generic one, and returns a new zero-arg function which is also generic, if the original one was. And that function will return the original function when called. That allows you to do this:
const f3 = specify(f1)<number>(); // const f3: (x: number) => number
specify(f1)
returns a function of type <T>() => (x: T) => T
. And thus specify(f1)<number>()
produces the non-generic (x: number)=>number
function. And it should work at runtime too:
const sixNumberStillOkay = f3(n); // okay
const sixStringStillNotOkay = f3(s); // error! string is not number
I'm not sure if you're going to be widening generic functions to concrete ones often enough for specify()
to be worth it; that's up to you.
Okay, hope that helps; good luck!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.