-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Polymorphic this
and Generics
#6223
Comments
@kitsonk this comment and the following discussion I think answers your question |
I agree that this is related with #6220 and I think TS should be able to express ES6 Promise subclassing behavior. |
@myitcv thanks, but I don't think it does. I am not talking about inferring higher-order types as seems to be discussed there. I am actually talking about replacing generic type slots with other values in a polymorphic To express where this becomes a problem, I will extend the class: class A<T> {
private items: T[] = [];
map<U>(callback: (item: T, idx: number, a: this) => U): A<U> {
// boring implementation details
});
}
class B<T> extends A<T> {
foo(): void {};
}
const b = new B<string>().map((item) => Number(item));
// b will be typed as A<number> not B<number> or class A<T> {
private items: T[] = [];
map<U>(callback: (item: T, idx: number, a: this) => U): this {
// boring implementation details
});
}
class B<T> extends A<T> {
foo(): void {};
}
const b = new B<string>().map((item) => Number(item));
// b will be typed as B<string> not B<number> |
This is taken from my comment in that thread: export interface Iterable<K, V> {
map<M>(
mapper: (value?: V, key?: K, iter?: this) => M,
context?: any
): Iterable<K, M>; // can't use 'this' here
} The comment Even though that particular comment doesn't refer to the problem of extending you refer to, if you look further up the thread you'll see this thread was motivated by exactly the same problem (as far as I can tell) |
class B<R, T, U> extends A<[C<R, T>]> { ... }
new B<number, string, boolean>().map<Date>(); // <-- what will be here? |
FYI #5999 |
@Igorbek I assume you are suggesting the declaration like this: class A<T> {
private items: T[] = [];
map<U>(callback?: (item: T, idx: number, a: this) => U): this {
// boring implementation details
return;
};
}
class B<R, T> extends A<[C<R, T>]> {
foo(): void {};
}
class C<K, V> {
map: [K, V][] = [];
}
const b = new B<number, string>().map<Date>(); Which works today and the question you are posing is that because the descent provides a different arity of generics, how would you know what to pass, but this is a problem for current polymorphic @Aleksey-Bykov plus #5845 and #1213 and #4967. It is a duplicate and I can understand the reasons behind it, which mostly seem to boil down to "well C# doesn't solve this problem either". But of course TypeScript is not C# and it seems to be a hole in the type system... Polymorphic I am fine if it gets closed as a duplicate, but I am just not sure if we are simply ignoring a problem... Is there a suggestion of another way of expressing the type? The previous answers appear to be summed up as "um, yeah we can't do that..." |
So the biggest question currently is what |
declare class Foo<T> {
foo(): this<T>;
}
declare class Bar extends Foo<void> { // Error: Foo requires subclasses to have 1 type parameters
} We can just give an error, no? |
@Aleksey-Bykov I think we might be overthinking things, for the sake of edge cases. In my mind it is simple. Polymorphic class A<T> {
map1<U>(callback: (item: T, idx: number, a: this) => U): this<U>;
map1<U>(callback: (item: T, idx: number, a: this) => U): A<U>;
map2<U>(callback: (item: T, idx: number, a: this) => U): this;
}
class B<K, V> extends A<[K, V]> {
}
class C<T> extends A<T> {
}
const b1 = new B<string, number>().map1<boolean>(); // Type is B<string, number>
const b2 = new B<string, number>().map2<boolean>(); // Type is A<boolean>
const b3 = new B<string, number>().map3<boolean>(); // Type is B<string, number>
const c1 = new C<string>().map1<boolean>(); // Type is C<boolean>
const c2 = new C<string>().map2<boolean>(); // Type is A<boolean>
const c3 = new C<string>().map3<boolean>(); // Type is C<string> Of course B is a problem, but it is a problem no matter what... none of the results accurately describe the intent of the code and some sort of type coercion is required. At least with C, we can accurately describe the intent of the code. In my mind all we are is choosing our own flavour of stupidity. I prefer the one where polymorphic |
I've seen a lot of people who change the arity in a subclass all the time. class Base<Dont, Know, Anything, Yet> { }
class Intermediate<More, Unknowns> extends Base<Oh, Yes, Now, ItsClear> { }
class Final<One, Last, Thing> extends Intermediate<All, Known> { } There will be a storm of issues with questions why the arity can't be changed just like now we see people puzzled why Bottom line is that the current syntax isn't capable enough to make this feature consistent. New syntax is required. New syntax is a big deal. The feature needs a strong justification and crave from the users. Not saying it's not possible and won't ever happen, my personal take is that HKT's are a better investment as they are more fundamental to the type system than |
A short brainstorming without HKT: declare class Base<T> {
property: T;
method<U>(): this<U>; // works as implicit type parameter restriction
}
declare class Subclass1<T, U> extends Base<U /* type parameter linked */> {
// property: U;
// method<V>(): Subclass<T, V /* linked */>;
}
declare class Subclass2<T, U> extends Base<void> {
// property: void;
// method<V>(): ?? <- Error, subclass should have linked type parameter
}
declare class Subclass3<T> extends Base<void> {
// property: void;
// method<V>(): ?? <- Error, subclass should have linked type parameter
}
declare class Subclass4 extends Base<void> {
// property: void;
// method<V>(): ?? <- Error, subclass should have linked type parameter
} |
@saschanaz looks like a good solution! +1 |
Yes. I'm dealing with this issue as well. I have now changed my code to use Without using |
Note: allowing a generic declare function use<T>(foo: Foo<T>): void
interface Foo<T> extends T {
one: string
}
const foo = {
one: "one",
two: 2,
}
use(foo) // works now
use<{two: number}>(foo) // equivalent If you were to allow |
@isiahmeadows when I try that code snippet in VSCode I get an error message under the |
Yeah, they fixed the inconsistency. The workaround is to use type intersection, but IMHO it's not a great workaround given it prevents me from assuming |
Checking in since it's been over a year...any new data on this? Are we stuck with intersection types for the indefinite future? |
what about that? declare class Base<T> {
property: T;
method<U>(): this<U>; // works as implicit type parameter restriction
}
declare class Subclass1<T, U> extends Base<U /* type parameter linked */> {
// property: U;
// method<V>(): Subclass<T, V /* linked */>;
}
declare class Subclass2<T, U> extends Base<void> {
// property: void;
// method<V>(): Subclass2<T, U> (extends Base<V>), so property type changed from void to V
}
declare class Subclass3<T> extends Base<void> {
// property: void;
// method<V>(): Subclass3<T> (extends Base<V>), so property type changed from void to V
}
declare class Subclass4 extends Base<void> {
// property: void;
// method<V>(): Subclass4<T> (extends Base<V>), so property type changed from void to V
} Can we change base class type parameters when we return |
First, thank you team for polymorphic
this
. It is really handy!I think I ran into a case though that I am finding challenging, when I need to return a
this
, but the generics might have changed. For example:Where I want to be able to change the generic type for the class with a function, but I will be contracting to return the "current" class, but I want to guard different generics. The following seems logical to me, but doesn't appear to be currently supported:
Where if no generics arguments are supplied, it is inferred to be the current ones, where as if they are supplied they are substituted.
The text was updated successfully, but these errors were encountered: