Skip to content

Instantly share code, notes, and snippets.

@david-driscoll
Last active December 11, 2015 04:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save david-driscoll/6af946050e87c7c25a5b to your computer and use it in GitHub Desktop.
Save david-driscoll/6af946050e87c7c25a5b to your computer and use it in GitHub Desktop.
Rest Generic Parameters
var s = '/*- *compute n:2-5* combineLatest<T, {|X|}>({_|x|: ObservableInput<|X|>}): Observable<[T, {|X|}]>; -*/\n\
/*- *compute n:2-5* combineLatest<T, {|X|}>(array: [{ObservableInput<|X|>}]): Observable<[T, {|X|}]>; -*/\
/*- *compute n:2-5* combineLatest<T, {|X|}, TResult>({_|x|: ObservableInput<|X|>}, project: (v1: T, {|x|: |X|}) => TResult): Observable<TResult>; -*/\
/*- *compute n:2-5* combineLatest<T, {|X|}, TResult>(array: [{ObservableInput<|X|>}], project: (v1: T, {|x|: |X|}) => TResult): Observable<TResult>; -*/';
var captureRegex = /\/\*\-(.*?)-\*\//g;
var computeNumberRegex = /\*compute n\:(\d.*?)\*/;
var tokenRegex = /\{.*?\}/g;
var capture;
var allItems = {};
while (capture = captureRegex.exec(s)) {
var result = capture[1];
var compute = computeNumberRegex.exec(result);
var range = compute[1].split('-');
if (range.length === 1) {
var start = 1;
var end = range[0];
} else {
var start = range[0];
var end = range[1];
}
result = result.replace(computeNumberRegex, '').trim();
var tokenResult;
for (var number = start; number <= end; number++) {
var res = result.replace(tokenRegex, function(capture, index, str) {
var items = [];
capture = capture.substr(1, capture.length - 2);
for (var i = start; i <= number; i++) {
items.push(capture.replace(/\|X\|/g, 'T' + i).replace(/\|x\|/g, 't' + i));
}
return items.join(', ');
});
(allItems[number] || (allItems[number] = [])).push(res);
}
}
for (var i in allItems) {
allItems[i].forEach(function(x) {
console.log(x);
});
}

Today with TypeScript some extreme modeling has to take place to get your API just right. This seems to effect libraries that deal with collections or collection like objects more often then other libraries (lodash, underscore, RxJS, etc). RxJS is the example case here.

Today modeling something like Observable.combineLatest or Observable.prototype.combineLatest can be extremely challenging, if you want types to flow through from your method, to their selector functions and so on.

Few notes:

  • ObservableInput<T> = Observable<T> | Promise<T> | Iterator<T> | ArrayLike<T>
  • also on this gist is a javascript file that will produce the appropriate output for the given arguments.

A simple declaration works, but then you loose type fidelity, for example see below.

static combineLatest<T[]>(...inputs: ObservableInput<T>[]): Observable<T[]>;
static combineLatest<T[], TResult>(...inputs: ObservableInput<T>[], selector: (...args: T[]) => TResult): Observable<TResult>;

// assumes inside interface / class of that defines T
combineLatest<TOthers[]>(...others: ObservableInput<T>[]): Observable<(T | TOthers)[]>;
combineLatest<TOthers[], TResult>(...others: ObservableInput<T>[], selector: (...args: T[]) => TResult): Observable<TResult>;

All these types can vary greatly, and infact they map 1-to-1 with their method calls, and return types.

Given T1, T2 and T3 using combineLatest as the basis, as the developer I know that...

  • Observable.combineLatest(t, t2, t3)
    • returns an observable tuple Observable<[T1, T2, T3]>
  • Observable.combineLatest(t, t2, t3, (v1, v2, v3) => v1+v2+v3)
    • v1 will be type T1
    • v2 will be type T2
    • v3 will be type T3
    • return type will be the result of my selector function

These types can be modeled correctly, but doing so requries extensive maintance of typings, relative to the number of possible arguments you want to map.

For example all the possible combinations of the prototype version of combineLatest ==

combineLatest<T, T2>(_t2: ObservableInput<T2>): Observable<[T, T2]>;
combineLatest<T, T2>(array: [ObservableInput<T2>]): Observable<[T, T2]>;
combineLatest<T, T2, TResult>(_t2: ObservableInput<T2>, project: (_t1: T, t2: T2) => TResult): Observable<TResult>;
combineLatest<T, T2, TResult>(array: [ObservableInput<T2>], project: (_t1: T, t2: T2) => TResult): Observable<TResult>;
combineLatest<T, T2, T3>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>): Observable<[T, T2, T3]>;
combineLatest<T, T2, T3>(array: [ObservableInput<T2>, ObservableInput<T3>]): Observable<[T, T2, T3]>;
combineLatest<T, T2, T3, TResult>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>, project: (_t1: T, t2: T2, t3: T3) => TResult): Observable<TResult>;
combineLatest<T, T2, T3, TResult>(array: [ObservableInput<T2>, ObservableInput<T3>], project: (_t1: T, t2: T2, t3: T3) => TResult): Observable<TResult>;
combineLatest<T, T2, T3, T4>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>): Observable<[T, T2, T3, T4]>;
combineLatest<T, T2, T3, T4>(array: [ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>]): Observable<[T, T2, T3, T4]>;
combineLatest<T, T2, T3, T4, TResult>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, project: (_t1: T, t2: T2, t3: T3, t4: T4) => TResult): Observable<TResult>;
combineLatest<T, T2, T3, T4, TResult>(array: [ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>], project: (_t1: T, t2: T2, t3: T3, t4: T4) => TResult): Observable<TResult>;
combineLatest<T, T2, T3, T4, T5>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, _t5: ObservableInput<T5>): Observable<[T, T2, T3, T4, T5]>;
combineLatest<T, T2, T3, T4, T5>(array: [ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>, ObservableInput<T5>]): Observable<[T, T2, T3, T4, T5]>;
combineLatest<T, T2, T3, T4, T5, TResult>(_t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, _t5: ObservableInput<T5>, project: (_t1: T, t2: T2, t3: T3, t4: T4, t5: T5) => TResult): Observable<TResult>;
combineLatest<T, T2, T3, T4, T5, TResult>(array: [ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>, ObservableInput<T5>], project: (_t1: T, t2: T2, t3: T3, t4: T4, t5: T5) => TResult): Observable<TResult>;

The static version is similar.

static combineLatest<T1>(_t1: ObservableInput<T1>): Observable<[T1]>;
static combineLatest<T1>(array: [ObservableInput<T1>]): Observable<[T1]>;
static combineLatest<T1, TResult>(_t1: ObservableInput<T1>, project: (t1: T1) => TResult): Observable<TResult>;
static combineLatest<T1, TResult>(array: [ObservableInput<T1>], project: (t1: T1) => TResult): Observable<TResult>;
static combineLatest<T1, T2>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>): Observable<[T1, T2]>;
static combineLatest<T1, T2>(array: [ObservableInput<T1>, ObservableInput<T2>]): Observable<[T1, T2]>;
static combineLatest<T1, T2, TResult>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, project: (t1: T1, t2: T2) => TResult): Observable<TResult>;
static combineLatest<T1, T2, TResult>(array: [ObservableInput<T1>, ObservableInput<T2>], project: (t1: T1, t2: T2) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>): Observable<[T1, T2, T3]>;
static combineLatest<T1, T2, T3>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>]): Observable<[T1, T2, T3]>;
static combineLatest<T1, T2, T3, TResult>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>, project: (t1: T1, t2: T2, t3: T3) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3, TResult>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>], project: (t1: T1, t2: T2, t3: T3) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3, T4>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>): Observable<[T1, T2, T3, T4]>;
static combineLatest<T1, T2, T3, T4>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>]): Observable<[T1, T2, T3, T4]>;
static combineLatest<T1, T2, T3, T4, TResult>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, project: (t1: T1, t2: T2, t3: T3, t4: T4) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3, T4, TResult>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>], project: (t1: T1, t2: T2, t3: T3, t4: T4) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3, T4, T5>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, _t5: ObservableInput<T5>): Observable<[T1, T2, T3, T4, T5]>;
static combineLatest<T1, T2, T3, T4, T5>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>, ObservableInput<T5>]): Observable<[T1, T2, T3, T4, T5]>;
static combineLatest<T1, T2, T3, T4, T5, TResult>(_t1: ObservableInput<T1>, _t2: ObservableInput<T2>, _t3: ObservableInput<T3>, _t4: ObservableInput<T4>, _t5: ObservableInput<T5>, project: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => TResult): Observable<TResult>;
static combineLatest<T1, T2, T3, T4, T5, TResult>(array: [ObservableInput<T1>, ObservableInput<T2>, ObservableInput<T3>, ObservableInput<T4>, ObservableInput<T5>], project: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => TResult): Observable<TResult>;

Essentially what we're seeing is a version of ES6 rest params, but for types. The above could be reduced to 4 simple typings, if TypeScript had a concept of "Rest Generic Parameters", if that name even make sense. :)

combineLatest<T, ...REST>(...args: ...ObservableInput<REST>): Observable<[T, ...REST]>;
combineLatest<T, ...REST>(array: [...ObservableInput<REST>]): Observable<[T, ...REST]>;
combineLatest<T, ...REST, TResult>(...args: ...ObservableInput<REST>, project: (_t1: T, ...args: ...REST) => TResult): Observable<TResult>;
combineLatest<T, ...REST, TResult>(array: [...ObservableInput<REST>], project: (_t1: T, ...args: ...REST => TResult): Observable<TResult>;

static combineLatest<...REST>(...inputs: ...ObservableInput<REST>): Observable<[...REST]>;
static combineLatest<...REST>(array: [...ObservableInput<REST>]): Observable<[...REST]>;
static combineLatest<...REST, TResult>(...inputs: ...ObservableInput<REST>, project: (...args: ...REST) => TResult): Observable<TResult>;
static combineLatest<...REST, TResult>(array: [...ObservableInput<REST>], project: (...args: ...REST) => TResult): Observable<TResult>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment