Let's modify the example function because the type <T>(x: T)=>void
doesn't provide any significant use with the generic type parameter. It behaves similarly to (x: unknown)=>void
, a concrete function type. Instead, we'll explore a function of type <T>(x: T)=>T
, which maintains the input type and produces an output value of the same type. This is where the difference lies, as (x: unknown)=>unknown
wouldn't 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
Your aim is to treat the generic function f1
as if it were a specific function f2
of type (x: number)=>number
. By doing this, you are widening the type of f1
, since every function of type <T>(x: T)=>T
can also be considered a function of type (x: number)=>number
, but not the other way around. The compiler acknowledges this widening and by manually annotating f2
with the extended type, you can achieve your goal:
const f2: (x: number) => number = f1; // no error
const sixNumberOkay = f2(n); // okay
const sixStringNotOkay = f2(s); // error! string is not a number
This process works effectively.
--
As previously highlighted, what you cannot do is automatically derive the type of f2
from the types used in f1
and
number</code. TypeScript lacks mechanisms to signify substituting types for type parameters directly. Neither <code>f1<number>
nor
typeof f1<number>
are valid expressions. TypeScript doesn't offer extensive support for
higher kinded types at the pure type level to accomplish this functionality.
In TypeScript 3.4, enhanced backing was introduced for inferring generic functions through other generic functions. While direct manipulations like the ones you mentioned aren't possible solely at the type level, you can create functions that execute these alterations seamlessly. For instance:
function specify<A extends any[], R>(f: (...a: A) => R) {
return () => f;
}
The specify()
function accepts any function, even a generic one, and returns a new zero-argument function that mirrors the initial function if it was generic. Upon invoking, this returned function reproduces the primary function. This enables the following usage:
const f3 = specify(f1)<number>(); // const f3: (x: number) => number
specify(f1)
yields a function of type <T>() => (x: T) => T
. Hence, specify(f1)<number>()
generates the non-generic (x: number)=>number
function. This setup should operate smoothly during runtime as well:
const sixNumberStillOkay = f3(n); // okay
const sixStringStillNotOkay = f3(s); // error! string is not a number
If you find yourself frequently broadening generic functions into specific ones, then using specify()
could prove beneficial, depending on your requirements.
I trust this information is helpful to you. Best of luck!
Link to code