I have these ts function:
const fnGeneric = <V,I>(fn:<U>(param:U) => V, param:I) => fn(param);
const fn = (some:string) => some;
const result = fnGeneric(fn,5);
but result ends with static type 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
What is wrong with this pattern? I think U should infer I type as number, but I have some blank space here.
I think your definition is off, the types will not be inferred automatically to fit your intent. This is the definition i would use:
const fnGeneric = <V,U>(fn: (param:U) => V, param: U) => fn(param);
const fn = (some: string) => some;
const result = fnGeneric(fn, 5);
This uses the same type parameter U
both in the function and the second argument, ensuring their compatibility. Of course the call will be invalid then, because fn
only accepts strings as argument.
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.
i can give u more realistic scenario, i made simplier version of fn, there is real:
export const inject = <I,V>(fn:<U>(input?:U) => V, resolveWithPayload: boolean, resolveArgs?: I) => <R>(payload:R):R => { resolveWithPayload ? fn(payload) : resolveArgs ? fn(resolveArgs) : fn(); return payload; };
const fn = (value:number):number => {
propertyToMutate = value;
return propertyToMutate;
}
const res =_fish.inject(fn,false,60)(50);
but calling it ends with :
Argument of type '(value: number) => number' is not assignable to parameter of type '(input?: U) => number'. Types of parameters 'value' and 'input' are incompatible. Type 'U' is not assignable to type 'number'.
if i improve code by your way:
export const inject = <I,V,U>(fn:(input?:U) => V, resolveWithPayload: boolean, resolveArgs?: I) => <R>(payload:R):R => { resolveWithPayload ? fn(payload) : resolveArgs ? fn(resolveArgs) : fn(); return payload; };
it ends by type definition error in inject itself like:
TS2345: Argument of type 'R' is not assignable to parameter of type 'U'.
TS2345: Argument of type 'I' is not assignable to parameter of type 'U'.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.