首页 > 解决方案 > 覆盖迭代器签名

问题描述

我想在 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 的所有优势。

标签: 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>.

当然,你只能写出你关心的方法和属性,但对我来说,这仍然是一件很困难的事情,因为它的好处值得怀疑。


好的,希望有帮助;祝你好运。


推荐阅读