Let's say I create a simple function to double its input,
> let f1: (x: number) => number = x => x * 2;
> .type f1
let f1: (x: number) => number
If I want to take the first value and double it, I can do either
let f2 = R.pipe( R.take(1), f1 );
let f2 = R.pipe( R.head, f1 );
Both of these work f2([5,4,3])
outside of TypeScript. However, with TypeScript I get,
> let f2 = R.pipe( R.take(1), f1 );
[eval].ts(6,29): error TS2345: Argument of type '(x: number) => number' is not assignable to parameter of type '(x: {}[]) => number'.
Types of parameters 'x' and 'x' are incompatible.
Type '{}[]' is not assignable to type 'number'.
> let f2 = R.pipe( R.head, f1 );
[eval].ts(6,26): error TS2345: Argument of type '(x: number) => number' is not assignable to parameter of type '(x: string) => number'.
Types of parameters 'x' and 'x' are incompatible.
Type 'string' is not assignable to type 'number'.
What am I doing wrong. I'm not even sure how to read,
Type '{}[]' is not assignable to type 'number'.
and, I don't see what cause the string
in
Type 'string' is not assignable to type 'number'.
(I don't see a string
anywhere).
The general issue behind this is that TypeScript does really poorly with higher-order generic functions: https://github.com/Microsoft/TypeScript/issues/9366.
In the majority of situations, when you pass a generic function (such as R.take(n)
or R.head
) into another (like R.pipe
), instead of preserving free generic type parameters, TypeScript will automatically infer them. A lot of the time it infers these parameters as {}
, its empty type.
In your second example with R.head
, it infers string because R.head
is actually defined with two overloads:
// from ramda/index.d.ts
head<T>(list: ReadonlyArray<T>): T | undefined;
head(list: string): string;
and since TypeScript can't decide yet which overload to use (you've given it neither a string nor an array) it just picks one. In my experience it picks the one defined last, more details on that here: https://github.com/Microsoft/TypeScript/issues/13570
To make your instances work, you're going to need to manually fill in the generic parameters to get TypeScript to stop making the wrong guesses:
let f4 = R.pipe<number[], number, number>( R.head, f1 );
For your example with R.take(n)
, according to Ramda it shouldn't actually work even if it typed, as it returns an array, not a singular value:
// from ramda/index.d.ts
take<T>(n: number): {
(xs: string): string;
(xs: ReadonlyArray<T>): T[];
};