Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript how to create a generic type alias for a generic function?

Tags:

Given a typed generic function, I want to create a generic alias for that function, but it seems I can't. In other words, this doesn't work:

// foo.tsx
function foo<T>(arg: T): T {
  return arg
}

type FooT = typeof foo  // works, but alias isn't generic: <T>(arg: T) => T
type FooParametersT = Parameters<typeof foo>  // sure: [unknown]
type FooReturnT = ReturnType<typeof foo>  // no problem: unknown

type GFooT<T,> = typeof foo<T,>  // yikes
type GFooParametersT<T,> = Parameters<typeof foo<T,>>  // nope
type GFooReturnT<T,> = ReturnType<typeof foo<T,>>  // fails

What I'm really trying to do is grab the type of a function in someone else's library and then build an interface around it. For example:

import { useState } from "react"

type UseStateFnT<T,> = typeof useState<T>
const wrapUseState: (toWrap: UseStateFnT<T,>) => UseStateFnT<T,> = …

Is this possible without recreating the complex typed function signature myself?

like image 835
posita Avatar asked Jul 03 '20 18:07

posita


People also ask

How do I create a generic type in TypeScript?

Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.

What is a type alias TypeScript?

In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.

What are the function of generic types in TypeScript?

Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.


1 Answers

UPDATE FOR TS4.7+

Hello again! TypeScript 4.7 will introduce instantiation expressions as implemented in microsoft/TypeScript#47607, which will allow you to directly specify the type parameters for a generic function without actually calling the function, so there's no longer a need to abuse classes the way shown in the rest of the answer. Here it is:

type GFooT<T,> = typeof foo<T>  // (arg: T) => T
type GFooParametersT<T,> = Parameters<typeof foo<T>>  // [arg: T]
type GFooReturnT<T,> = ReturnType<typeof foo<T>>  // T

That's almost the same as the syntax in the question except that trailing commas are not supported after type arguments (but they are supported after type parameter declarations). Hooray!

Playground link to code


PREVIOUS ANSWER FOR TS4.6-

There are two different flavors of generics in TypeScript: generic functions, and generic types... and it looks like you want the compiler to transform one into the other for you, which isn't directly supported.


To be clear:

Generic types have type parameters that need to be specified before you can use them as a specific type. For example:

type GenType<T> = (x: T) => T[];
declare const oops: GenType; // error
declare const genT: GenType<string>; // okay
const strArr = genT("hello"); // string[];
const numArr = genT(123); // error!

Here, GenType is a generic type. You need to specify the type parameter to use it as the type of a value, and then the resulting type is no longer generic. The genT function takes a string and returns a string[]. It cannot be used as a function that takes a number and returns a number[].


Generic functions, on the other hand, have a specific type that can act like any possible substitution of its type parameters. The value of a generic function type is still generic when you use it. The type parameter is attached to the call signature:

type GenFunc = <T>(x: T) => T[];
declare const genF: GenFunc;
const strArr = genF("hello"); // strArr: string[];
const numArr = genF(123); // numArr: number[];

Here, GenFunc is a specific type referring to a generic function. The genF function is still generic when it is called.

Generic functions (including generic constructor functions) can be thought of as generic values, as opposed to generic types.


These two flavors of generics are related to each other but the TypeScript type system isn't expressive enough to talk about how they are related. In some other language, you might be able to define one in terms of the other like

type GenFunc = forall T, GenType<T>; // not TS, error

or

type GenType<T> = instantiate GenFunc with T; // not TS, error

but in TypeScript you can't. Maybe if we ever get higher kinded types as requested in microsoft/TypeScript#1213... but not now. So it is not directly possible to turn GenFunc into GenType programmatically in the type system.


There are evil awful ways to coerce the compiler to calculate GenType in terms of GenFunc. The way I know of makes use of generic class property initialization and some higher order type inference for generic functions introduced in TypeScript 3.4. I'm making the compiler think it's calculating values when I don't actually have any, and then getting the type of one of these pretend values:

class GenTypeMaker<T> {
    getGenType!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
    genType = this.getGenType(null! as GenFunc)<T>()
}
type GenType2<T> = GenTypeMaker<T>['genType']
// type GenType2<T> = (x: T) => T[]

You can verify that GenType2<T> is the same type as GenType<T>, and if you change GenFunc into any generic function with one type parameter, GenType2<T> will change accordingly.

But I don't know I'd want to recommend anyone actually use this method. And it doesn't really scale or compose; if you have an object full of generic functions in one type parameter and you want to transform it to an object full of specific functions with a specified type parameter, there's no way to use this method to get it from the type system without doing it once for each object property.


Playground link to code

like image 93
jcalz Avatar answered Oct 14 '22 23:10

jcalz