Как переписать определение универсального конструктора, который не соответствует сигнатуре - PullRequest
0 голосов
/ 28 июня 2018

Я пишу библиотеку для реализации лениво оцененных запросов итерируемых типов в JavaScript. Я знаю, что уже есть доступные варианты для достижения этой цели, но у меня совершенно другой подход к раскрытию методов расширения.

Я испытываю затруднения при подходе к конкретной задаче проектирования, и мне было интересно, есть ли какой-нибудь обходной путь, о котором я не думал для решения проблемы.

Мое Enumerable<T> определение класса и его интерфейс вместе с несколькими помощниками выглядит следующим образом:

// Iterator<T> defined in lib.es2015.iterable.d.ts
interface IteratorFunction<T> {
  (this: IEnumerable<T> | void): Iterator<T>
}

interface CompareFunction<T> {
  (a: T, b: T): number
}

// inspired by pattern for ArrayConstructor in lib.es5.d.ts
interface IEnumerableConstructor {
  new <T>(iterator: IteratorFunction<T>): IEnumerable<T>
  new <T>(iterator: IteratorFunction<T>, compare: null): IEnumerable<T>
  new <T>(iterator: IteratorFunction<T>, compare: CompareFunction<T>): IOrderedEnumerable<T>

  // static methods...
}

// Symbol.compare defined in separate file as a declaration merge to SymbolConstructor
interface IOrderedEnumerable<T> extends IEnumerable<T> {
  readonly [Symbol.compare]: CompareFunction<T>

  // overrides for thenBy()...
}

// IterableIterator<T> defined in lib.es2015.iterable.d.ts
interface IEnumerable<T> extends IterableIterator<T> {
  // member methods...
}

class Enumerable<T> implements IEnumerableConstructor {
  readonly [Symbol.iterator]: IteratorFunction<T>
  readonly [Symbol.compare]: (null | CompareFunction<T>)

  constructor (iterator: IteratorFunction<T>)
  constructor (iterator: IteratorFunction<T>, compare: (null | CompareFunction<T>) = null) {
    this[Symbol.iterator] = iterator
    this[Symbol.compare] = compare
  }
}

Проблема в том, что

constructor (iterator: IteratorFunction<T>)

относится к гипотетическому определению

new (iterator: IteratorFunction<T>): IEnumerable<T>

в отличие от предоставленного определения

new <T>(iterator: IteratorFunction<T>): IEnumerable<T>

и т. Д. Для других перегруженных объявлений в IEnumerable<T>, сообщая мне об ошибке

Тип 'Enumerable<T>' не соответствует сигнатуре 'new <T>(iterator: IteratorFunction<T>): IEnumerable<T>'.

Я подумал, что изменение класса IEnumerableConstructor также должно быть общим, но потом я столкнулся с проблемой, когда попытался сделать это:

declare global {
  // ...

  interface ArrayConstructor extends IEnumerableConstructor/*<T>*/ { } // illegal!
  interface MapConstructor extends IEnumerableConstructor { }
  interface SetConstructor extends IEnumerableConstructor { }
  interface StringConstructor extends IEnumerableConstructor { }
  interface TypedArrayConstructor extends IEnumerableConstructor { }

  interface Array<T> extends IEnumerable<T> { }
  interface Map<K, V> extends IEnumerable<[K, V]> { }
  interface Set<T> extends IEnumerable<T> { }
  interface String extends IEnumerable<string> { }
  interface TypedArray extends IEnumerable<number> { }
}

Для пояснения, я объявил TypedArray как класс, который Int8Array и т. Д. Расширяется, согласно спецификации ECMAScript (но типизации для него не предоставлены в файлах TypeScript lib.*.d.ts, поскольку он никогда не используется напрямую , что и понятно).

Как я уже сказал, эта реализация библиотеки представляет собой принципиально иной подход к предоставлению методов расширения встроенным в JavaScript, и я знаю обо всех подводных камнях, но сейчас я ищу способ предоставить определение для конструктора класса Enumerable<T>, который учитывает универсальный тип IEnumerable<T>. Есть идеи?

1 Ответ

0 голосов
/ 28 июня 2018

IEnumerableConstructor имеет сигнатуру конструкции, такой интерфейс не может быть напрямую реализован классом .

Но в TypeScript класс не нужно явно объявлять как реализующий некоторый интерфейс - в конце концов, система типов является структурной, и вы можете просто использовать свой класс там, где требуется интерфейс, при условии, что он совместим.

Из того, что я понимаю в вашем коде, Enumerable<T> должен фактически реализовывать IEnumerable<T>, потому что это тип, который объявлена ​​для создания сигнатуры конструкции в interface IEnumerableConstructor. Таким образом, он должен иметь метод next(), и тогда он будет работать, если вы просто опустите implements IEnumerableConstructor (но не помешает, если вы замените его на implements IEnumerable, чтобы компилятор проверил, что класс правильно реализует этот интерфейс) :

class Enumerable<T>  implements IEnumerable<T> {
  readonly [Symbol.iterator]: IteratorFunction<T>
  readonly compare: (null | CompareFunction<T>)

  constructor (iterator: IteratorFunction<T>)
  constructor (iterator: IteratorFunction<T>, compare?: (null | CompareFunction<T>)) {
    this[Symbol.iterator] = iterator
    this.compare = compare
    }

    next(): IteratorResult<T> {
        return undefined;
    }
}

const C: IEnumerableConstructor = Enumerable;

type A = { a: string };
let a: A[];

const c = new C(a.values); // inferred as const c: IEnumerable<A>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...