Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript Function with Generic Return Type

type FuncGenericReturn = <T>() => T;
const funcReturnsNumber: FuncGenericReturn = (): number => 1;

(Sandbox)

Getting this error:

Type 'number' is not assignable to type 'T'. 'number' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.(2322) input.ts(1, 26): The expected type comes from the return type of this signature.

I would expect typescript to automatically infer T as number and just use it. Why is it complaining? What is the proper way to write something like this? Thanks.

like image 283
Lawrence Lau Avatar asked Feb 11 '20 22:02

Lawrence Lau


People also ask

How do I create a return type function in TypeScript?

To define the return type for the function, we have to use the ':' symbol just after the parameter of the function and before the body of the function in TypeScript. The function body's return value should match with the function return type; otherwise, we will have a compile-time error in our code.

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.

How do you pass a generic type as parameter TypeScript?

Assigning Generic Parameters By 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 ReturnType TypeScript?

The ReturnType in TypeScript is a utility type which is quite similar to the Parameters Type. It let's you take the return output of a function, and construct a type based off it.


2 Answers

It's important to pay attention to where the generic type parameters are declared and what scope they have. The type

type FuncGenericReturn = <T>() => T;

is a concrete type referring to a generic function. <T>() => T means: "a function whose caller specifies a type T and which returns a value of type T." This is essentially impossible to implement safely. Imagine if you had such a function:

declare const funcGenericReturn: FuncGenericReturn;

Then you should be able to call it this way:

const someNumber: number = funcGenericReturn<number>(); 
const someString: string = funcGenericReturn<string>();

But of course at runtime those will both compile to

const someNumber = funcGenericReturn();
const someString = funcGenericReturn();

Meaning that funcGenericReturn() would just have to "know" at runtime that it should first return a number and then a string, based on type information which is erased before the JavaScript is generated. So properly implementing a FuncGenericReturn would require magical foreknowledge.

To reiterate: when you have a generic function, the generic type parameters are specified by the caller, not by the implementer. It's true that sometimes the compiler will infer these type parameters so that the person writing the code doesn't have to spell it out, but again, these inferences are happening at call time. Two different calls to the same generic function could end up having two different choices for the type parameters.


Let's compare this to a different but related type definition:

type FuncConcreteReturn<T> = () => T;

Here, FuncConcreteReturn is a generic type referring to a concrete function. It would be more accurate to say that FuncConcreteReturn is not really a type; it's more like a type operator which takes an input type T and produces an output type () => T.

For any particular type T, the type FuncConcreteReturn<T> is a concrete function type which takes no parameter and returns a value of type T. So a FuncConcreteReturn<string> is a function that takes no arguments and returns a string, while a FuncConcreteReturn<number> is a function that takes no arguments and returns a number. Note that FuncConcreteReturn<string> is a different type from FuncContreteReturn<number>, and neither of them are a FuncConcreteReturn because that's not a valid type. So the following is valid:

const funcReturnsNumber: FuncConcreteReturn<number> = () => 1;
const funcReturnsString: FuncConcreteReturn<string> = () => "";

Again, funcReturnsNumber is not a generic function. It is a concrete function that always returns a number. And FuncConcreteReturn<T> is a generic type, where the value of T is chosen when the type is written out. Since these types are function types, the type T is chosen by the implementer of these functions, and not by the caller.


By the way, the relationship between a generic function type like

type G = <T, U>(t: T, u: U) => [T, U]

and a generic type like

type H<T, U> = (t: T, u: U) => [T, U]

is that any instance of the former will be an instance of the latter, but not vice versa. This means that if you did have a FuncGenericReturn, you could assign it to a value of type FuncConcreteReturn<string> or a FuncConcreteReturn<number>:

const fn: FuncConcreteReturn<number> = funcGenericReturn; // okay
const fs: FuncConcreteReturn<string> = funcGenericReturn; // okay

Or, for the G and H types above, you could do this:

const g: G = <T, U>(t: T, u: U) => [t, u];
g("a", 1); // okay
g(1, "a"); // okay

const h1: H<string, number> = g; // okay
h1("a", 1); // okay
h1(1, "a"); // error

const h2: H<number, string> = g; // okay
h2(1, "a"); // okay
h2("a", 1); // error

Okay, I hope that gives you some understanding on the difference between generic functions and generic types. Good luck!

Playground link to code

like image 124
jcalz Avatar answered Oct 16 '22 09:10

jcalz


Isn't this syntax working for you?

type FuncGenericReturn<T> = () => T;
const funcReturnsNumber: FuncGenericReturn<number> = () => 1;

like image 39
Ashish Patel Avatar answered Oct 16 '22 11:10

Ashish Patel