Search code examples
javascripttypescriptfunctional-programmingtype-inferencecurrying

Avoid type variable infered as unknown for manually curried functions


I usually curry my function manually through arrow syntax. There seems to be an issue with type inference as soon as generic type parameters come into play:

type Const<A, B> = { const: A };

const Const = <A, B>(x: A): Const<A, B> => ({ const: x });

const map = <F, A, B>(f: (_: A) => B) => (tx: Const<F, B>) => tx;

const inc = (x: number) => x + 1;

const foo = map(inc) (Const("foo")); // Const<unknown, number> instead of <string, number>

Playground

When I uncurry map everything works as expected. How can I keep my functions curried without losing type inference?


Solution

  • You just need to move type parameters to the function where they are actually used as parameters.

    In this case F has no inference site in the map, it is only used in the return type (inferring from called return type, is not something TS does, it does infer from return type in other scenarios but not this). Moving the type parameter to the function returned by map lets the second call infer F and produces the expected type.

    type Const<A, B> = { const: A };
    
    const Const = <A, B>(x: A): Const<A, B> => ({ const: x });
    
    const map = <A, B>(f: (_: A) => B) => <F>(tx: Const<F, B>) => tx;
    
    const inc = (x: number) => x + 1;
    
    const foo = map(inc) (Const("foo")); 
    

    Playground Link