In a library I have a helper function that works with arrays of any type. Now I want to extend that to accept also typed arrays, but I cannot find a common base class, nor are typed arrays generic. This is the code I have so far for generic arrays and one typed array:
public static copyOf(original: Int32Array, newLength: number): Int32Array;
public static copyOf<T>(original: T[], newLength: number): T[];
public static copyOf<T>(original: T[] | Int32Array, newLength: number): T[] | Int32Array {
if (original instanceof Int32Array) {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Int32Array(newLength);
result.set(original);
return result;
} else {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Array<T>(newLength);
result.splice(0, 0, ...original);
return result;
}
}
This approach would however require to add an own branch for every typed array type, which would defeat the idea of generics. How can this be implemented in a more compact way?
CodePudding user response:
First for the typings, since this is TypeScript. There isn't currently a built in type corresponding to typed arrays; see microsoft/TypeScript#15402. But nothing stops you from defining your own as a union of the relevant individual types. Here's one possibility:
type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array |
Uint32Array | Uint8ClampedArray | Float32Array | Float64Array;
I haven't included BigInt64Array
or BigUint64Array
in there, because they don't have the same exact behavior as the others (you need bigint
and not number
), but you could add those if you need to.
Now for the implementation. One approach is to use the constructor
property of the typed array you pass in to get the right class constructor, and use it to construct a new instance. TypeScript won't be happy about you doing this, since it doesn't model strongly typed constructor
properties (see ms/TS#3841 for a lengthy discussion about this)... so we'll need to use a type assertion to convince the compiler that it's acceptable.
Possibly like this:
class Foo {
public static copyOf<T extends TypedArray>(original: T, newLength: number): T;
public static copyOf<T>(original: T[], newLength: number): T[];
public static copyOf<T>(original: T[] | TypedArray, newLength: number): T[] | TypedArray {
if (!Array.isArray(original)) {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new (original.constructor as new (arg: number) => TypedArray)(newLength);
result.set(original);
return result;
} else {
if (newLength < original.length) {
return original.slice(0, newLength);
}
if (newLength === original.length) {
return original.slice();
}
const result = new Array<T>(newLength);
original.forEach((v, i) => result[i]=v); // splice doesn't do what you want
return result;
}
}
}
where instead of checking for instanceof
to check that the value is a particular typed array, I check with the Array.isArray()
method to check that the value isn't a built-in array.
(Also note that I had to change away from the splice()
method which inserts things, causing the resulting array to be longer than you intend.)
If you needed to support the BigInt64Array
and BigUint64Array
then you'd have to add a type assertion on the result.set(original);
line to prevent an error.
Let's make sure it works:
const arr = Foo.copyOf(new Int16Array([1, 2, 3, 4, 5]), 10);
// const arr: Int16Array
console.log(arr) // Int16Array: ...
const arr2 = Foo.copyOf(["a", "b", "c"], 4);
// const arr2: string[]
console.log(arr2) // ["a", "b", "c", ]
Looks good.