Home > database >  Typescript omit generic parameter when not needed
Typescript omit generic parameter when not needed

Time:12-01

I've created a generic function type with a return parameter which is entirely dependent on the input parameter types, and the generic parameter has an extends clause. Here's a dummy version:

type Foo = {}
type MyFunc <T extends Foo> = (a: T, b: T) => T[];

const bar: MyFunc<number> = (a: number, b: number) => [a,b];
const baz: MyFunc<boolean> = (a: boolean, b: boolean) => [a,b];

I'm using the generic parameter to ensure 1) a and b extend Foo and 2) a and b are the same type.

The problem I'm running into is with taking a MyFunc as a parameter type. I don't need an exact type for T; I only need to ensure the parameter constraints are satisfied when it is called. For example, I'd like to do the following:

const makeArray = (qux: MyFunc) => qux(1,2);

1 and 2 are of the same type and both extend Foo, so I know the function call will work and I'll get back a number[]. However, the above doesn't compile. TypeScript says:

Generic type 'MyFunc' requires 1 type argument(s)

If I use (qux: MyFunc<any>), then no type-checking at all occurs.

Is there any other syntax I can use to tell TypeScript to check the input parameters but not to require specifying the exact type ahead of time?

CodePudding user response:

It looks like you've got the scope of the generic type parameter wrong. There are two distinct (but related) "flavors" of generics in TypeScript. See TypeScript how to create a generic type alias for a generic function? and typescript difference between placement of generics arguments for example.

There are generic types, where the type parameter is declared on the type name itself, like

// type GenType<T> = ...T...

And there are generic functions, where the type parameter is declared on the call signature, like

// type GenFunc = <T>(...T...)=>...

You originally had MyFunc as a generic type that evaluates to a non-generic function

// original
type MyFunc<T extends Foo> = (a: T, b: T) => T[];

but that doesn't seem to be what you want. Instead, you should make it a non-generic type that evaluates to a generic function:

// changed
type MyFunc = <T extends Foo>(a: T, b: T) => T[];

So now you can make a function of type MyFunc and it will act like your old MyFunc<T> for every possible T:

// actually generic
const foo: MyFunc = (a, b) => [a, b];

If you really want to restrict the function so that it takes some specific type argument, you can use instantiation expressions:

const bar = foo<number>; 
// const bar: (a: number, b: number) => number[]
const baz = foo<boolean>; 
// const baz: (a: boolean, b: boolean) => boolean[]

But I don't know how useful that is.


Anyway, now you can implement makeArray how you want it, where MyFunc does not require a type argument:

const makeArray = (qux: MyFunc) => qux(1, 2);
// const makeArray: (qux: MyFunc) => number[]

And look, the compiler infers that the return type of makeArray is number[], as desired!

Playground link to code

  • Related