I'm writing a library to implement lazily evaluated queries of iterable types in JavaScript. I'm aware there are already available options that accomplish this, but mine is taking a drastically different approach to exposing the extension methods.
I'm having difficulty approaching a particular design challenge and was wondering if there was some work-around I hadn't thought of for resolving the issue.
My Enumerable<T>
class definition and its interface along with a few helpers is as follows:
// 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
}
}
The problem is that
constructor (iterator: IteratorFunction<T>)
refers to the hypothetical definition
new (iterator: IteratorFunction<T>): IEnumerable<T>
as opposed to the provided definition
new <T>(iterator: IteratorFunction<T>): IEnumerable<T>
and so on for the other overloaded declarations in IEnumerable<T>
, providing me the error
Type '
Enumerable<T>
' provides no match for the signature 'new <T>(iterator: IteratorFunction<T>): IEnumerable<T>
'.
I considered changing the IEnumerableConstructor
class to be generic as well, but then I run into a problem later when I try to do this:
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> { }
}
To clarify, I've declared TypedArray
as the class that Int8Array
and so on extend, as per the ECMAScript specification (but the typings for it are not provided in TypeScript lib.*.d.ts
files since it's never directly used, which is understandable).
As I've said, this library implementation is a drastically different approach to exposing extension methods to built-ins in JavaScript and I'm aware of all the pitfalls, but right now I'm looking for a way to provide a definition for the class Enumerable<T>
constructor that respects the generic type of IEnumerable<T>
. Any ideas?