TypeScript rejection with `Type '{}[]' is

2019-08-18 03:18发布

问题:

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).

回答1:

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[];
};