Reputation: 14526
I work with typed arrays a lot and a lot of my functions really should be able to work with any sort of typed array (e.g., summing a Uint8Array
or a Float32Array
). Sometimes, I can get away with just a simple type union, but often I keep running into the same error.
A simple example:
type T1 = Uint8Array;
type T2 = Int8Array;
type T3 = Uint8Array | Int8Array;
// No problems here:
const f1 = (arr: T1) => arr.reduce((sum, value) => sum + value);
const f2 = (arr: T2) => arr.reduce((sum, value) => sum + value);
// Does not work:
const f3 = (arr: T3) => arr.reduce((sum, value) => sum + value);
The error on f3
is:
Cannot invoke an expression whose type lacks a call signature. Type '
{
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number): number;
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue: number): number;
<U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8Array) => U, initialValue: U): U;
} | {
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number): number;
(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue: number): number;
<U>(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int8Array) => U, initialValue: U): U;
}' has no compatible call signatures.ts(2349)
According to the docs:
If we have a value that has a union type, we can only access members that are common to all types in the union.
The way I'm using reduce
here is common to all arrays, but I surmise the problem is the optional 4th argument (which for Uint8Array.prototype.reduce
is a Uint8Array
but for Int8Array.prototype.reduce
is an Int8Array
).
Is there a simple work-around for this? Or do I need to write a generics implementation for each of map
, reduce
, filter
?
Upvotes: 6
Views: 2342
Reputation: 250396
There has always been an issue in invoking unions of functions. Until recently the rule was that no invocation would be allowed at all, but since 3.3 (PR) invocation is allowed with some caveats. The big one you are hitting here is that if the constituents of the union both have generic signatures the call will still not be allowed. So for example on simple arrays one forEach
can be called (no generic type parameters on it), while reduce
can't be called (since both reduce
from string[]
and from number[]
have a generic type parameter):
declare let o: string[] | number[];
o.forEach((e: number | string) => console.log(e)); // ok
o.reduce((e: number | string, r: number | string) => e + ' ' + r) //err
This does mean having a union of array types is difficult to use and only allows invocation of a very small set of methods (most Array methods have generic type parameters).
This also applies to Uint8Array
and Int8Array
which while don't inherit array have most of the same methods.
There is no good solution here, the simplest work around is to assert the variable to one of the types and go with that (assuming you don't use the array
callback parameter it should be ok)
const f3 = (arr: T3) => (arr as Uint8Array).reduce((sum, value, array /* no good inferred to Uint8Array */) => sum + value);
Or fall back to one of the functions you can invoke
const f4 = (arr: T3) => {
let sum = 0;
(arr as Uint8Array).forEach((val)=> sum + val)
}
Upvotes: 3
Reputation: 1504
There is simple workaround. Declare interface with common method signature
type T3 = Uint8Array | Int8Array;
interface HasReduce {
reduce(c: (p: number, n: number) => number): number; // common callback signture with 2 arguments
}
function someLogic(arr: HasReduce): number {
return arr.reduce((sum, value) => sum + value);
}
declare var v : T3;
someLogic(v); // OK
So you can declare HasMap, HasFilter and combine them.
Upvotes: 2