Reputation: 1610
What is the difference between generic function syntax:
type Identity<T> = (t: T) => T
and
type Identity = <T>(t: T) => T
?
Upvotes: 7
Views: 747
Reputation: 219
In my experience:
type Type <T> = (t: T) => T
, it's like a function, you must give a parameter to make the T
sensible.type Type = <T> (t: T) => T
, it's just make another name for a <T> (t: T) => T
type, and here, the T
will decided by the value you give to the place of t
, and you can't give parameter to decide it.For a generic function, like const a = <T,> (t: T): T => t
,you can define a type a = <T> (t: T) => T
then write const a: a = <T,> (t: T): T => t
for it:
type a = <T> (t: T) => T
const a: a = <T,> (t: T): T => t
// same as const a: <T> (t: T) => T = <T,> (t: T): T => t
The type of T
decided by the function it self.
If you have some necessary to make this T
decided on something other, not just the function itself, like:
type aa <T> = { head: T, tail: (t: T) => T }
Then the <T>
should at left side of =
when you define its type alias. Here, the T
also can be decided by tail
, the function it self, but the type of the param of this (t: T) => T
typed function must equal with the type of value which at head
field. Where ever you change, you must make them equal, or this defile of type alias should be changed also.
e.g.
You can define:
type Fn <T, R> = (x: T) => R ;
type Pipeyard <T> = <R,> (continuation: Fn<T, R>) => Pipeyard<R> ;
const Pipeyard =
<T,> (head: T)
: Pipeyard<T> =>
( <R,> (continuation: Fn<T, R>)
: Pipeyard<R> =>
Pipeyard (continuation(head))
) as Pipeyard <T> ;
There is <T>
left side the =
and <R>
right side of it.
That define can use like this:
Pipeyard (3)
(x => `x: ${x}`)
(x => "~ " + x)
(console.log); // "~ x: 3"
The derivation of the types is automatic ! Cause the <R>
which is the next <T>
is just decided by the return type of the function you gives.
(And these codes is under agpl-3.0 license from pure-symbols/pure.closures repo 🙃)
Upvotes: 4
Reputation: 328302
There are two distinct flavors of generics in TypeScript: generic functions and generic types.
A generic function declares its generic type parameter (or parameters) on the call signature of the function:
type IdentityGenFunc = <T>(t: T) => T
The type parameter on a generic function (T
above) isn't specified until the function is actually called, at which point the caller specifies it (or the compiler infers it on behalf of the caller). This means that a generic function implementation must be able to deal with any possible specification of T
that the function caller wants:
const idGenFunc: IdentityGenFunc = x => x;
const resultABC = idGenFunc("ABC"); // T inferred here as ABC
// const resultABC: "ABC"
const result123 = idGenFunc(123); // T inferred here as 123
// const result123: 123
A generic type declares its generic type parameter (or parameters) on the type declaration:
type IdentityGenType<T> = (t: T) => T
The type parameter on a generic type (T
above) must be specified before you can have a value of that type. If a generic type has a call signature or method that refers to the type parameter, that type parameter is fixed as soon as the surrounding generic type is specified, before that method is called. Once you are talking about, say, an IdentityGenType<string>
, then its call signature only needs to be able to handle a string
. And so to implement that function you do not need to deal with any possible value of T
:
const idGenTypeString: IdentityGenType<string> = x => x + "!";
const resultABCagain = idGenTypeString("ABC"); // okay, "ABC" is assignable to string
// const resultABCagain: string
const result123Again = idGenTypeString(123); // error! 123 is not assignable to string
These two flavors of generics are related to each other. The type IdentityGenFunc
is essentially an "infinite intersection" of IdentityGenType<T>
for every possible T
. The TypeScript type system isn't quite expressive enough to say that:
// the following is not valid TS, but this is what you want to say
type IdentityGenFunc = forall T, IdentityGenType<T>; // not valid TS, error
but you can witness it by assignments:
// every IdGenFunc is also an IdGenType<number>
const idGenTypeNumber: IdentityGenType<number> = idGenFunc; // okay
On the other hand, you can think of IdentityGenType<T>
as the same as IdentityGenFunc
instantiated with some type T
. Again, the type system won't let you express this:
// the following is not valid TS, but this is what you want to say
type GenType<T> = instantiate GenFunc with T; // not TS, error
Although TypeScript 4.7 will introduce instantiation expressions which will let you instantiate the type parameters on a value whose type is a generic function:
// if you instantiate a value of type IdGenFunc with Date, then you get an IdGenType<Date>
const idGenTypeDate: IdentityGenType<Date> = idGenFunc<Date>; // okay for TS4.7+
Upvotes: 15