typescript - 覆盖迭代器签名
问题描述
我想在 Array 对象上创建一个包装器以更轻松地处理二维数组。一切都很好,但我也想覆盖Symbol.iterator
以简化与 2D 数组有关的嵌套循环。
我希望能够基本上像这样循环数组
const m = new Matrix([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
for (let { c, r, value } = m) {
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
我已经为此编写了代码并且它有效。然而,打字稿抱怨他的迭代器签名,因为它与 Array 应该有的不匹配。
这是我的代码
class Matrix<T=number> extends Array<Array<T>> {
// Other methods...
*[Symbol.iterator]() {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield { c, r, value: this[r][c] as T };
}
}
}
}
打字稿抱怨Type '() => IterableIterator<[number, number, T]>' is not assignable to type '() => IterableIterator<T[]>'.
你可以在这里看到它
我的问题是:如何在没有 Typescript 抱怨的情况下编写此代码?我可以通过将值转换为来停止编译器,Any
但在这种情况下我失去了使用 Typescript 的所有优势。
解决方案
尝试按照您的要求去做将是一件令人头疼的事情,因为它违背了类型系统的精神。有一个叫做Liskov 替换原则的想法,它说这A extends B
意味着你应该被允许在任何预期实例的A
地方使用实例。B
或者,换句话说,每个 的实例A
也是的一个实例B
。
通过这样说Matrix<T> extends Array<Array<T>>
,您声称 aMatrix<T>
是a Array<Array<T>>
。Array<Array<T>>
但是如果我在一个for...of
循环中迭代,我希望循环遍历Array<T>
元素。这是Array<Array<T>>
. 如果出现 type 的元素, 则说明[number, number, T]
出现问题: aMatrix<T>
不是. 里氏替换原则被违反了。 Array<Array<T>>
处理此问题的简单且推荐的方法是Matrix<T> extends Array<Array<T>>
通过单独保留迭代器方法来实现,并且只需添加一个生成所需迭代器的unroll()
方法。Matrix<T>
这种添加的方法不会违反替代原则,因为“没有unroll()
方法”不是Array<Array<T>>
合同的一部分。
像这样的东西:
class Matrix<T> extends Array<Array<T>> {
constructor(data: T[][] = []) {
super();
// Fill given data to matrix;
for (let r = 0; r < data.length; r++) {
this[r] = [];
for (let c = 0; c < data[r].length; c++) {
this[r][c] = data[r][c];
}
}
}
*unroll(): IterableIterator<[number, number, T]> {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield [c, r, this[r][c]];
}
}
}
}
const m = new Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
for (let [c, r, value] of m.unroll()) {
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
但是如果你真的想用你的自定义实现覆盖迭代器,你能做到吗?是的,我猜。你不能违约Array<Array<T>>
,所以你必须写一份新的。您可以尝试使用一些类型操作,如映射类型和条件类型来表达“一个Array<Array<T>>
但没有定义的迭代器方法”,然后断言Array
构造函数也是该类型事物的构造函数,然后扩展它:
type _SortOfArray<T> = Pick<
Array<Array<T>>,
Exclude<keyof Array<any>, keyof IterableIterator<any>>
>;
interface SortOfArray<T> extends _SortOfArray<T> {}
interface SortOfArrayConstructor {
new <T>(): SortOfArray<T>;
}
const SortOfArray = Array as SortOfArrayConstructor;
class Matrix<T> extends SortOfArray<T> {
constructor(data: T[][] = []) {
super();
// Fill given data to matrix;
for (let r = 0; r < data.length; r++) {
this[r] = [];
for (let c = 0; c < data[r].length; c++) {
this[r][c] = data[r][c];
}
}
}
// Other helper methods...
*[Symbol.iterator](): IterableIterator<[number, number, T]> {
for (let r = 0; r < this.length; r++) {
for (let c = 0; c < this[r].length; c++) {
yield [c, r, this[r][c]];
}
}
}
}
const m = new Matrix([['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']]);
for (let [c, r, value] of m) {
// c is a number, r is a number, value is a string
console.log(`Column: %s, Row: %s, Value: %s`, c, r, value);
}
这一切都有效,万岁?好吧,不完全是:
const filteredM = m.filter(row => row[0]!=='4');
// filteredM is a string[][] at compile time, but Matrix<string> at runtime!
for (let hmm of filteredM) {
// compiler thinks hmm[0] is a string, but it's really a number
console.log(hmm[0].toUpperCase()); // no compiler error, runtime error!!
}
看,Array
扩展的工作方式,返回新数组的方法实际上默认返回数组的扩展版本(它被称为数组的种类)。而且,如果 aMatrix<T>
真的是a Array<Array<T>>
,那么这个亚种替换就可以了。但是我们把它改成了别的东西,所以现在所有Matrix<T>
返回新数组的方法都输入错误了。
事实上,为了让它“正确”,我们必须手动完全写出新合约:
interface SortOfArray<T> {
[n: number]: Array<T>;
length: number;
toString(): string;
toLocaleString(): string;
pop(): T[] | undefined;
push(...items: T[][]): number;
concat(...items: ConcatArray<T[]>[]): SortOfArray<T>;
concat(...items: (T[] | ConcatArray<T[]>)[]): SortOfArray<T>;
join(separator?: string): string;
reverse(): SortOfArray<T>;
shift(): T[] | undefined;
slice(start?: number, end?: number): SortOfArray<T>[];
sort(compareFn?: (a: T[], b: T[]) => number): this;
splice(start: number, deleteCount?: number): SortOfArray<T>;
splice(start: number, deleteCount: number, ...items: T[]): SortOfArray<T>;
// ... and on and on and on ...
呃,我什至不知道我是对的还是犯了一些错误。这对我来说真的不值得。更不用说Array<Array<T>>
如果你把它交给一个Matrix<T>
.
当然,你只能写出你关心的方法和属性,但对我来说,这仍然是一件很困难的事情,因为它的好处值得怀疑。
好的,希望有帮助;祝你好运。
推荐阅读
- python - UnboundLocalError:分配前引用的局部变量“嵌入”
- apache-kafka - Kafka Producer:发送消息后断开连接与保持连接打开
- vhdl - 函数内的 VHDL 计算
- javascript - (Javascript)给定间隔中的素数:for循环不迭代
- android - 如何为 xmpp 实现 smack
- cryptocurrency - Diem(前 Libra):创建帐户失败并显示“无法从验证器获取帐户,错误:航点值不匹配”
- python-3.x - 在 python 中不能安全地解释为整数
- c# - C#按列分组并与其他列形成分层数据
- python - 为什么使用 .items() 允许我遍历 defaultdict 否则变量被读取错误?
- python - 创建动态范围并计算平均值