Generic function that return value of same T or different R can't accept higher order function '( i : T ) => i' as default parameter value

Benjamin Vincent

I would like to understand how to get where I want with generic type so this is my example :

function foo(value: 42 = 42 ,bar:(val: any) => any = I => I){
return bar(value)
}

I work with a value of some type T in my example the type is a number so I am using 42 I want to have a function as an argument that will change the value to a different type R or return a same type T which in this case would be the identity 'I => I' as the default parameter value.

I rewrote my function as an arrow function like this:

const foo = <R1>(
  value: number = 42,
  bar: <R0>(val: number) => R0 = I => I
) => {
  return bar<R1>(value);
};

const foo0 = (
  value: number = 42,
  bar: <R0>(val: number) => R0 = I => I
) => {
  return bar(value);
};

const foo1 = <R1>(
  value: number = 42,
  bar: (val: number) => R1 = I => I
) => {
  return bar(value);
};


ERROR: Type 'number' is not assignable to type 'RX'. 'RX' could be instantiated with an arbitrary type which could be unrelated to 'number'.

I am using R1 and R0 because I am not sure the difference between R one and R naught in this example but if I remove the I => I default value the error message go away but I don't understand why or how to overcome it...

in the below example fooB(42,i=>i) and foo0B(42,i=>i) erored out but not foo1B(42,i=>i)

How can I set the default value I => I without having to specify both R | number

const fooB = <R1>(value: number = 42, bar: <R0>(val: number) => R0) => {
  return bar<R1>(value);
};

const foo0B = (value: number = 42, bar: <R0>(val: number) => R0) => {
  return bar(value);
};

const foo1B = <R1>(value: number = 42, bar: (val: number) => R1) => {
  return bar(value);
};


fooB(42,i=>i)
foo0B(42,i=>i)
foo1B(42,i=>i)

Jeffrey Westerkamp

The error message that you get from your examples is caused by the fact that type arguments can be explicitly given when calling a function, for example:

fooB<number>(42, (i) => i);

considering fooB:

const fooB = <R1>(value: number = 42, bar: <R0>(val: number) => R0) => {
    return bar<R1>(value);
};

what happens here is that internally bar can be called with any other type besides R1 too. If that is the case there is no guarantee that number (the type of value) is a type that's compatible with R0.

The problem arises when we'd do for example:

fooB<21>(42, (i) => i);

About this call we can say: R1 is instantiated with/as 21.

Here 42 extends number as it's supposed to considering the type of value, but number extends 21 is FALSE. In other words: 42 and 21 are different subtypes of type number. Therefore the returned i is not assingable to R1 nor R0. A similar problem occurs with foo0B and R0.

There's already some good coverage on why this error message occurs.


The fix

If I understand correctly, you want a function that:

  • Takes an argument of type T
  • Optionally takes a function fn that takes an argument of type T and returns a value of type U
  • Calls fn with the first argument, and then returns the result (U) if fn is given, or returns T otherwise (the identity).

The most straightforward way to write this is like the following:

function foo<T, U>(value: T, fn: (val: T) => T | U = i => i): T | U {
    return fn(value);
}

But here both foo and fn can indeed only return the union type T | U, since there's only one signature to account for both the cases

  • Without fn
  • With fn

If you want branching of types depending on whether a callback is passed (I'm assuming this based on the default I => I) you can use function overloading instead. Essentially it's like saying "I have the following 2 signatures for foo":

function foo<T>(value: T): T;
function foo<T, U>(value: T, fn: (val: T) => U): U;
function foo<T>(value: T, fn = (i: T) => i) {
    return fn(value);
}

The first function signature is for when no callback is passed: it takes a T, and will just return a T. In that sense foo is like the identity function.

The second signature takes the callback fn. If no type arguments are explicitly given to a call to foo, U is going to be inferred from the type of fn. Otherwise the type of fn will be validated against the given type for U.

Then you have the function implementation. It can account for both signatures, and does so using the identity function (default value) to match the first signature, or a given function to match the second. This implementation signature only explicitly specifies the types that are constant for all overloads, so only T is specified. The overloads will do the rest by determining the return type on a per-call basis.

Here's a complete TS playground using the following examples:

// Without callback
const a: number = foo(42);             // Fine
const b: string = foo("I'm a string"); // Fine
const c: string = foo(42);             // ERROR: type '42' is not assignable to type 'string'

// With callback
const d: number = foo('42', parseInt);                  // Ok
const e: string = foo(42, (x: number) => x.toString()); // Ok
const f: string = foo(42, (x: number) => x + 1);        // ERROR: type 'number' is not assignable to type 'string'

// With explicit types
const g: number = foo<string, number>('42', parseInt);  // Ok
const h: number = foo<string, number>('42', (x) => x);  // ERROR: type 'string' is not assignable to type 'number'

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Higher order function in java, how to write a method that take no parameter and return a Map which Map's value is a method i java

SimpleJdbcCall for MySql Function yields "Can't set IN parameter for return value of stored function call"

Default function parameter value visible in template but it shouldn't (gcc)

Why can't a default parameter and function parameter have the same name?

Why can't I return an arrow function?

Why can't I concatenate and print the value returned by a function?

Weird examples in javascript, why I can't reassign a global variable's value through a function's parameter?

I can't return String value in Java

Why can't I use an anonymous function for returning a color value?

Why can't use let to declare a variable using the same name as one function parameter, even the parameter get set default value?

Why can't I call a generic function with a union type parameter?

Kotlin default generic parameter of function return value

Can't make created python function return a value which I can then use in another calculation

Why can't I change the default value of a function after it was defined?

Why can't I return the generic type T from this function?

Why can't i replace the function name by it's return value in this example?

mcc function can't return value,why?

Can't use function return value in write

Why I use list as a parameter of a function and the function can't change the value of the actual parameter but only the formal parameter?

How can I programmatically determine the default value of a function parameter?

How do I return a value from a function where the type of the passed variables aren't the same

Why can't I assign lambdas to function parameters as a default value?

Why i can't return value from recursive function?

I can't understand how the logic flow of this ES6 higher-order function

two function with same functionality return different value because i haven't use "return" keyword inside one function exact before function call

Why can't I call a function as parameter of other functions with the same return values as parameters?

I can't assign value to a generic variable with same type

I can't pass a value into Timer function in GLUT

I don't understand about higher-order function parameter passed in example

TOP Ranking

HotTag

Archive