Literate Goggles
Literate Goggles

Reputation: 386

Generic function type alias

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

Answers (1)

jcalz
jcalz

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.

Link to code

Upvotes: 13

Related Questions