如何在一些签名不兼容的情况下通用处理TypedArrays?

huangapple go评论57阅读模式
英文:

How can I handle TypedArrays generically when some of their signatures are incompatible?

问题

Take the simple function below. This is trivial in JavaScript, but in TypeScript this produces an error because TypedArray.set is expecting the first parameter to be of type ArrayLike<number> & ArrayLike<bigint>.

type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray 
    | Int16Array | Uint16Array
    | Int32Array | Uint32Array 
    | BigInt64Array | BigUInt64Array
    | Float32Array | Float64Array

function expand<T extends TypedArray>(arr: T, elementCount: number): T {
    const constructor = arr.constructor as new (length: number) => T
    const result = new constructor(arr.length + elementCount)
    result.set(arr) //error
    return result
}
英文:

Take the simple function bellow. This is trivial in JavaScript, but in TypeScript this produces an error because TypedArray.set is expecting the first parameter to be of type ArrayLike&lt;number&gt; &amp; ArrayLike&lt;bigint&gt;.

<!-- language: lang-typescript -->

type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray 
| Int16Array | Uint16Array
| Int32Array | Uint32Array 
| BigInt64Array | BigUInt64Array
| Float32Array | Float64Array

function expand&lt;T extends TypedArray&gt;(arr: T, elementCount: number): T {
    const constructor = arr.constructor as new (length: number) =&gt; T
    const result = new constructor(arr.length + elementCount)
    result.set(arr) //error
    return result
}

答案1

得分: 1

以下是您提供的内容的中文翻译:


像这样的东西只能通过泛型才能实现。在microsoft/TypeScript#15402中有一个长期存在的开放功能,支持带有超类型接口的类型化数组,其中评论建议定义一个类似这样的泛型接口:

interface TypedArr<T extends number | bigint> {
  readonly BYTES_PER_ELEMENT: number;
  readonly buffer: ArrayBufferLike;
  readonly byteLength: number;
  readonly byteOffset: number;
  /* ✂ ⋯ 省略部分 ⋯ ✂ */
  join(separator?: string): string;
  keys(): IterableIterator<number>;
  lastIndexOf(searchElement: T, fromIndex?: number): number;
  readonly length: number;
  /* ✂ ⋯ 省略部分 ⋯ ✂ */
  reverse(): this;
  set(array: ArrayLike<T>, offset?: number): void;
  slice(start?: number, end?: number): TypedArr<T>;
  /* ✂ ⋯ 省略部分 ⋯ ✂ */
  valueOf(): TypedArr<T>;
  values(): IterableIterator<T>;
  [Symbol.iterator](): IterableIterator<T>;
  [index: number]: T;
}

然后你可以使用 TypedArr<number>TypedArr<bigint> 来表示你在联合类型中需要的大部分功能:

function expand<T extends number | bigint>(arr: TypedArr<T>, elementCount: number): TypedArr<T> {
  const constructor = arr.constructor as new (length: number) => TypedArr<T>
  const result = new constructor(arr.length + elementCount);
  result.set(arr);
  return result;
}

declare const a64: BigInt64Array;
expand(a64, 3); // 可行
declare const a32: Int32Array;
expand(a32, 4); // 可行

这可能永远不会成为标准库的一部分,但即使它不是,你也可以在你的代码库中自己定义它。这并不是最佳的体验,但作为解决方法,它的表现方式都是相同的。

或者,根据你的需求,你可以创建一个仅捕获你在函数内部将使用的TypedArray部分的类型。也许像这样:

interface TypedArrLike extends ArrayLike<number | bigint> {
  set(array: ArrayLike<number | bigint>, offset?: number): void;
}

function expand<T extends TypedArrLike>(arr: T, elementCount: number): T {
  const constructor = arr.constructor as new (length: number) => T
  const result = new constructor(arr.length + elementCount)
  result.set(arr)
  return result
}

declare const a64: BigUint64Array;
expand(a64, 3); // 可行
declare const a32: Int32Array;
expand(a32, 4); // 可行

这将产生一个类似的结果,而无需一个庞大的接口。

[代码的 Playground 链接](https://www.typescriptlang.org/play?target=99#code/HYQwtgpgzgDiDGEAEApeIA2AvJBvAUEkqJLAsgGICuGGehRSAlsAC4QBOAZuUgCoBPGBAAmAQQ4cAPHyQQAHu2AioxKmABGnJAB8kGpgHMWrAHz1GjDhBAiA9sAwCkAIQCafAKIBlAPoAFTwAlX08AGU8AWU8AOT4ALjVNTgBuBktrWwcnfSouLk5EiQ4QARc8go4wpgBrCDTLIkz7R2cNAXYwiGBDVgALROB1LQ4Gxubsto6IAHl8qAhWQeHU9MZ4OxgBAHUmfpYAClYQDkNF5eSOABokKGOOVgB+C5Gb7pFnpJGASkT9qDGlm6rA4TGgB1+SAAkuwShoMBAYZwQKw7NIANpDS43PgAXVMgMYEAAbpwBAcYNYREx0OxEgdiZgqBBEnwbiwRAoXpwbicSgJWUJRMUZKZvkgALzmDR2OwIkDAG7-CSGT4KgSQmVymzAQlELhMWgMpks-g3O4nJ7c65yZSfLE-P59JgAtb6w2wilUmko03GjDM1ns5Rcr48pB80qC4TiSSi8VSiPAARK51QFVq5OQwQxkV8AlupAG5Re0Q+ulIf2Bs3MEPya28yRR-hC2PSfMJ6Wy+WKpDK06ZjWs3RIKgh4uiPVFjlQuul6m0v2MgOmtm1zn1sM2yMClu5uMdyVd7UK1MujOJdWQh2rRpFtGeBB9A7oWgaBA1LjAenL6trjmhje25NruObCgeYpHkgxJ2EwIhnumA6XlmiQwXBU4sPAAaclABwLCc8B9J4CKQGwQZFhwdhgLOG72isHCat2OoYXWcx4TYHCEcRECkUsNZcJR1F1nRlzXvRU4AFawcA7FwCUqIcJ8dygj0kLKSwhhTnUAi4ZCSJwgi+komiUhAQWd4YCAdw0QobH4ZxREkcC5ECVRNnyCJjpblOEytEgCI9P01pTmAIAwC+mAYO+8Cft+la-quwYbg2EYgdG4HtpBiZrv2qrIUOe4ZaKPmiFQiARW+H5fvSlIkkwdhUFAABqJrkfAVCSMCLUrm1HXWGw7kpTu6V

英文:

The only way for something like this to work is with generics. There is a longstanding open feature at microsoft/TypeScript#15402 to support some sort of supertype interface for typed arrays, with a comment that suggests defining a generic interface like:

interface TypedArr&lt;T extends number | bigint&gt; {
  readonly BYTES_PER_ELEMENT: number;
  readonly buffer: ArrayBufferLike;
  readonly byteLength: number;
  readonly byteOffset: number;
  /* ✂ ⋯ omitted for brevity ⋯ ✂ */
  join(separator?: string): string;
  keys(): IterableIterator&lt;number&gt;;
  lastIndexOf(searchElement: T, fromIndex?: number): number;
  readonly length: number;
  /* ✂ ⋯ omitted for brevity ⋯ ✂ */
  reverse(): this;
  set(array: ArrayLike&lt;T&gt;, offset?: number): void;
  slice(start?: number, end?: number): TypedArr&lt;T&gt;;
  /* ✂ ⋯ omitted for brevity ⋯ ✂ */
  valueOf(): TypedArr&lt;T&gt;;
  values(): IterableIterator&lt;T&gt;;
  [Symbol.iterator](): IterableIterator&lt;T&gt;;
  [index: number]: T;
}

and then you can use TypedArr&lt;number&gt; or TypedArr&lt;bigint&gt; to represent most of the functionality you needed in your union type:

function expand&lt;T extends number | bigint&gt;(arr: TypedArr&lt;T&gt;, elementCount: number): TypedArr&lt;T&gt; {
  const constructor = arr.constructor as new (length: number) =&gt; TypedArr&lt;T&gt;
  const result = new constructor(arr.length + elementCount);
  result.set(arr);
  return result;
}

declare const a64: BigInt64Array;
expand(a64, 3); // okay
declare const a32: Int32Array;
expand(a32, 4); // okay

This may or may not ever make it into the standard library, but even if it doesn't you can just define it yourself for your code base. That's not an optimal experience, but as workarounds go it's pretty good since it behaves the same either way.


Or, depending on your needs, you could make a type that captures only those pieces of TypedArray that you will use inside your function. Maybe like:

interface TypedArrLike extends ArrayLike&lt;number | bigint&gt; {
  set(array: ArrayLike&lt;number | bigint&gt;, offset?: number): void;
}

function expand&lt;T extends TypedArrLike&gt;(arr: T, elementCount: number): T {
  const constructor = arr.constructor as new (length: number) =&gt; T
  const result = new constructor(arr.length + elementCount)
  result.set(arr)
  return result
}

declare const a64: BigUint64Array;
expand(a64, 3); // okay
declare const a32: Int32Array;
expand(a32, 4); // okay

which produces a similar result without needing a huge interface.

Playground link to code

huangapple
  • 本文由 发表于 2023年7月18日 08:27:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76708837.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定