Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic types in higher order functions

Tags:

typescript

I would like to use a generic type as the parameter in the inner function of a higher order function. However, when I do this, the parameter(s) on the outer function lose their types (become any).

Here's an example of what I'm trying to do. I expect to get an error on line 2.

type TestFunc = <T>(outerArg: string) => (innerArg: T) => number
const test: TestFunc = <T>(outerArg) => (innerArg: T) => innerArg ? outerArg * 3 : 0 // no error because `outerArg` is `any` (unexpected)

When innerArg is not generic, I get the error I expect:

type TestFunc = (outerArg: string) => (innerArg: boolean) => number
const test: TestFunc = (outerArg) => (innerArg: boolean) => innerArg ? outerArg * 3 : 0 // error because `outerArg` is `string` (expected)

Question

On line 2 in the first example, outerArg has type any. On line 2 in the second example, outerArg has type string. Why are they different?


More details

My goal is to allow T to be specified when evaluating the outer function. For example:

type TestFunc = <T>(outerArg: string) => (innerArg: T) => number
const test: TestFunc = (outerArg) => (innerArg) => innerArg ? outerArg.length * 3 : 0 
const test2: TestFunc = <T>(outerArg) => (innerArg: T) => innerArg ? outerArg * 3 : 0 // want error


const fourBool = test<boolean>('str')
console.log(fourBool(true))
console.log(fourBool(1)) // want error

const fourNum = test<number>('str')
console.log(fourNum(true)) // want error
console.log(fourNum(1))

Here it is on typescript playground.

like image 581
Eric Avatar asked Oct 17 '22 08:10

Eric


2 Answers

You have two functions:

1) A function with a generic type that returns a number,

2) and a function that takes a number and returns a generic.

As you mentioned in the comments, you're using an arrow function with a type parameter, but that won't work for both functions. You can declare your inner function as a generic, and your outer function with a type parameter:

type InnerFunc<T> = (inner: T) => number;
type TestFunc = <T>(outer: number) => InnerFunc<T>;

const test: TestFunc = <T>(outerArg: number) => (innerArg: T) => innerArg ? outerArg * 3 : 0;

const fourBool = test<boolean>(4);
fourBool(true); // OK
fourBool(1);    // error
like image 192
chrisbajorin Avatar answered Nov 14 '22 15:11

chrisbajorin


It appears that this might be a bug in the compiler or by design.

But this might work as a work around for now where you attach the generic type to the function that uses the generic type.

type TestFunc = (outerArg: string) => <T>(innerArg: T) => number

OK, this is the way you would want to approach it to do what you want:

type TestFunc = (arg1: string) => <T>(a: T) => number
type Number1 = (a: number) => number
const test: TestFunc = (arg1) => (a) => a ? (arg1 ? 1 : 2) : 0
const num1: Number1 = test("blah")
// OR
const newNum1 = <Number1>test("bleh")

Above will give you the error if you place arg1 like you did before and it will get you the generic form you wanted before. You will notice if you define the "sub type" incorrectly it will also give a compilation error.

For completeness here is a simpler solution (pretty much what cdbajorin did in his solution):

type TestFunc1 = <T>(arg1: string) => (a: T) => number
const test1: TestFunc1 = (arg1: string) => a => a ? (arg1 ? 1 : 2) : 0
const newNum2 = test1<number>("bluh")
like image 26
Jon49 Avatar answered Nov 14 '22 15:11

Jon49