Home > Mobile >  Typed arrays in generic functions
Typed arrays in generic functions

Time:12-24

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.

Playground link to code

  • Related