英文:
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<number> & ArrayLike<bigint>
.
<!-- language: lang-typescript -->
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
}
答案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); // 可行
这将产生一个类似的结果,而无需一个庞大的接口。
英文:
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<T extends number | bigint> {
readonly BYTES_PER_ELEMENT: number;
readonly buffer: ArrayBufferLike;
readonly byteLength: number;
readonly byteOffset: number;
/* ✂ ⋯ omitted for brevity ⋯ ✂ */
join(separator?: string): string;
keys(): IterableIterator<number>;
lastIndexOf(searchElement: T, fromIndex?: number): number;
readonly length: number;
/* ✂ ⋯ omitted for brevity ⋯ ✂ */
reverse(): this;
set(array: ArrayLike<T>, offset?: number): void;
slice(start?: number, end?: number): TypedArr<T>;
/* ✂ ⋯ omitted for brevity ⋯ ✂ */
valueOf(): TypedArr<T>;
values(): IterableIterator<T>;
[Symbol.iterator](): IterableIterator<T>;
[index: number]: T;
}
and then you can use TypedArr<number>
or TypedArr<bigint>
to represent most of the functionality you needed in your union type:
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); // 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<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); // okay
declare const a32: Int32Array;
expand(a32, 4); // okay
which produces a similar result without needing a huge interface.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论