简体   繁体   中英

in typescript, how to define a specialized version of a generic function?

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!

Link to code

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM