The reason for this becomes more clear if you look at the type signature of fnGeneric
as it would appear in a .d.ts
file.
declare const fnGeneric: <V,I>(fn:<U>(param:U) => V, param:I) => V
Does not relate U
and I
in any way. Here is what the type checker can infer from this signature:
fnGeneric
is a function defined for all types V
, and I
, receiving 2 parameters fn
and param
and returning the type V
fn
is a function defined for all types U
receiving 1 parameter param
, of type U
and returning V
param
is of type I
This type-checks fine, because fn
is a function that can turn any type into a V
, so passing it an I
in the definition fn(param)
is perfectly acceptable.
The problems start when you try to call this function. In order to call fnGeneric
, you need to give is a function that can take any type, and turn it into the type you want to get out of fnGeneric
. This is impossible! Passing in a const fn: (some: string) => string
like in your example doesn't work, becase fn
doesn't accept any type, it only accepts strings. The signature would need to be const fn: <U>(some: U) => string
. Hence the error:
Argument of type '(some: string) => string' is not assignable to parameter of type '(param: U) => string'. Types of parameters 'some' and 'param' are incompatible. Type 'U' is not assignable to type 'string
Additionally, based on your example fnGeneric(fn, 5)
, you are trying to pass a number
to a function that takes a string
by coercing fn
to be a function that takes any type of argument. Here is a code snippet that type checks:
const fnGeneric = <A>(fn: <B>(param: B) => B, param: A): A => fn(param);
const fn = <A>(some: A): A => some;
const result: number = fnGeneric(fn, 5); // result = 5
fn
in this example is commonly known as the identity
function - which returns the parameter it is given, and it is the only valid implementaion (without arbitrary type casting) of a function with the signature <A>(a: A) => A
. By extension, fnGeneric
is a function that takes the identity function and a parameter of any type, and returns the application of the identity function to that parameter.
What is wrong with this pattern?
fnGeneric
as defined doesn't make any sense, it would be impossible to write a program that type checks that satisfies the signature. The only way to make the types work simply makes it redundant. There aren't any cases where it would be preferable to call fnGeneric(identity, x)
instead of just identity(x)
(or for that matter, simply the expression x
). They are all completely equivalent.
U
V
andI
defined? – Titian Cernicova-Dragomirconst fnGeneric = <V,I>(fn:<U>(param:U) => V, param:I) => fn(param);
sorry editor somehwow remove it – Ondrej Basistaconst fnGeneric = <V,I, U>(fn:(param:U) => V, param:U) => fn(param); const fn = (some:string) => some; const result = fnGeneric(fn,"5");
?fn
should not be itself a generic function .. – Titian Cernicova-Dragomir