Reputation: 386
Why in TypeScript type alias doesn't work with a generic function? For example, here TS doesn't define type Identical as generic.
type Identical = <T>(v: T) => T;
const identical: Identical<string> = (v) => v
I know that correct variant is:
type Identical<T> = (v: T) => T;
But why first example doesn't work and what type of T
?
Upvotes: 7
Views: 4154
Reputation: 330346
In what follows I will use the word "specific" to mean "not generic". Usually people say "concrete" for this, but I'm concerned that someone will think that means "not abstract
", and this has nothing to do with abstract
classes.
Other than generic functions, TypeScript only has generic types, not generic values. For a generic type, the type parameter is written in angle brackets after the type name:
type GenericType<T> = {x: T};
You can have a generic type like Foo<T>
, but any actual value of that type must be specific, with an actual specific type specified as T
:
declare const badValue1: GenericType; // error, requires 1 type argument
declare const badValue2: GenericType<T>; // error, cannot find name 'T'
declare const goodValue: GenericType<string>; // okay
Note that GenericType<string>
is now a specific type, equivalent to {x: string}
. So once you specify generic parameters in a generic type by plugging in specific types, you get a specific type out.
Generic functions are different: a value of a generic function type is generic. It acts as the full family of different specific function types. For a generic function type, the type parameter is written in angle brackets before the function parameter list:
type GenericFunction = <T>(x: T, y: T) => void;
A generic function's type is not necessarily generic itself; there is no type parameter on the GenericFunction
name above. So you cannot specify the generic type parameter by addingYou specify the generic function type parameter only when you call the function:
declare const badFunc: GenericFunction<string>; // error, GenericFunction is not generic
declare const goodFunc: GenericFunction; // okay
const ret = goodFunc<string>("okay", "fine"); // okay, type parameter specified as string
const ret2 = goodFunc("okay", "fine"); // also okay, type parameter inferred as string
So, the difference between these:
type IdGenericType<T> = (x: T) => T;
type IdGenericFunc = <T>(x: T) => T;
is that the first is a generic type which will refer to a specific function when specified, while the second is a specific type which refers to a generic function. These types are not equivalent, although they are related. You can assign a value of type IdGenericFunc
to any variable of type IdGenericType<XXX>
for any specific type XXX
you want:
let id: IdGenericFunc = x => x;
let idString: IdGenericType<string> = id; // okay
But not vice versa:
const otherId: IdGenericFunc = idString; // error! T not assignable to string
Which makes sense, because an IdGenericType<string>
is only known to accept and output a string
:
idString = x => x + "!"; // okay
So you can't assume that an IdGenericType<string>
is a valid IdGenericFunc
. The relationship between IdGenericType<T>
and IdGenericFunc
is that IdGenericFunc
is essentially the intersection of IdGenericType<T>
for all possible T
:
// type IdGenericFunc = forall T. IdGenericType<T>; // invalid syntax
But there's no way to express that directly in TypeScript (I borrowed the forall
syntax from Haskell).
See the TypeScript GitHub issue on generic values, microsoft/TypeScript#17574 for more info.
Upvotes: 13