This is a draft for TypeScript Proposal to introduce generic type constrains on member level See microsoft/TypeScript#1290
-
-
Save Igorbek/21b2bd503fd291d3281def829e2d5fbd to your computer and use it in GitHub Desktop.
Additional explanation and examples + possible usage taken from the duplicateMicrosoft/TypeScript#7083:
It's currently possible to do this:
method<A extends Array<number>>(one):number;
method<A extends Array<string>>(two):string;
But not possible to do this:
interface Example<T extends Array<number>> {
method(one):number;
}
interface Example<T extends Array<string>> {
method(two):string;
}
This means we cannot have different typings depending on the generic type constraint of the interface. I've even tried a hack like this:
interface Example<T extends Array<any>> {
method<A extends T & Array<number>>(one):number;
method<A extends T & Array<string>>(two):string;
}
I was hoping that TS will infer the type of the T array from the intersect, unfortunately that does not happen (it offers both methods, regardless of whether Example is an instance of Example<Array<number>>
or of Example<Array<string>>
).
Among other uses, this feature would be useful for a variety of database libraries that return internal types that can have their own operations done on them. For example, when I map
an DbArray<DbNumber>
, I should get a different set of methods than when operating on DbArray<DbString>
.
The current workaround is to ask the user to explicitly declare both - the outer and the inner type, e.g.:
interface Example<TOuter, TInner> {
map<TOut>((item: TInner) => TOut): TOut;
// ...TOuter used in another method
}
However, this TInner
is redundant (it's already inside of TOuter) and sometimes not relevant at all.
With more complex types this can cause the introduction of even more generic types, that grow like cancer in the declarations (interface Example<A, B, C, D, ...>
) - the more possibilities there are, the more explicit declarations need to be done at the interface level. This has been an obstacle to creating proper type definitions for RethinkDB DefinitelyTyped/DefinitelyTyped#4551.
Last example: an interface DbValue
can be a generic of any primitive type in the database: an array
, a string
or a number
.
For an instance of an array
, you'd need to pass in DbValue<Array<number>, number>
, yet for an instance of string, the second parameter is irrelevant: DbValue<string, void>
.
What we'd need is to be able to constrain not only by method's own generics, but by the containing interfaces generics, for method signatures themselves, something like:
interface Example<T> {
// here the constraint is not of the method, but of the type
method(one):number where T extends Array<number>;
method(two):string where T extends Array<string>;
}
Original issue: microsoft/TypeScript#1290 Authors: @igorbek, @niieani Area: Language spec
This is a suggestion for spec change.
Sometimes type's members could only be applicable with some restrictions of enclosing type parameter (generic type constraints).
For instance, this is use case of RxJS:
var xs: Observable<Observable<string>>;
xs.mergeAll(); // mergeAll is applicable here
var ys: Observable<string>;
ys.mergeAll(); // mergeAll is NOT applicable here
Original Rx.NET Merge method implemented as C# extension method on type IObservable<IObservable<T>>
. In RxJS it's implemented as an instance method.
Current RxJS typescript definition just use unsafe trick:
export interface Observable<T> {
...
mergeAll(): T;
...
}
But it can be still unsafely called for as instance of any Observable<T>
.
Something like C# extension methods. But don't think it's useful in such cases.
declare extensions {
mergeAll<T>(this Observable<Observable<T>> source): Observable<T>;
}
BTW, extensions method could be other cool feature, which change call method (from instance-like to static-like).
interface A<T> { x(): void; }
interface A<T extends B> { y(): void; }
var a: A<number>;
var b: A<B>;
a.x(); b.x(); // ok
b.y(); // ok
a.y(); // error
interface A<T> {
x(): void;
y<T extends B>(): void; // only applicable if T extends B
z<T extends B>: number; // for any property
// alternative syntax options:
<T extends B>z: number; // 1
z: number where T extends B; // 2
y(): void where T extends B;
}
I'll suggest this option. Moreover the previous solution options is special case of this one (incompatible constraints are merged on member level).