Reputation: 593
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
Upvotes: 1
Views: 532
Reputation: 327624
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!
Upvotes: 2
Reputation: 11969
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
Upvotes: 1