Closed rwyborn closed 3 years ago
override could potentially be used on fields, if they're being re-declared in the subclass body. Optional interface methods aren't overrides so are outside of the scope of what we're talking about here.
On Thu, 14 Apr 2016 at 11:58, Olmo notifications@github.com wrote:
What about setting inherited fields vs redefining new ones? React state is a good example.
Or implementing optional interface methods?
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/2000#issuecomment-209879217
Optional interface methods aren't overrides so are outside of the scope of what we're talking about here.
Optional interface methods are a concept fairly unique to TypeScript as far as I know, and I think a TypeScript implementation of "overrides" should apply to them to.
In the case of React lifecycle methods like componentDidMount, these are optional interface methods that have no implementation in the superclass.
In the case of React lifecycle methods like componentDidMount, these are optional interface methods that have no implementation in the superclass.
Exactly, so they don't override anything. I think we're getting confused about what the override
keyword is intended to provide here, because if you're just looking for a way of getting better code hinting/intellisense there are other ways that can be achieved without adding new keywords to the language.
Guys, with all due respect, can we please stay focused. I think that discussing alternative keywords is counterproductive, especially since the Issue was started with a specific request.
Can we all either agree that we need override
and ask nicely the Typescript team to add it or drop the request and let the Issue closed?
I think it's always useful to pose the request in the context of what the problem is, there's always different ways to solve the problem. The sticking point with override was around it being mandatory or not (some making the point that it isn't mandatory in C++). The problem a lot of people have referenced is getting the name right for overridable functions (which may or may not be implemented in the super class) - React's lifecycle functions being the main problem.
If override doesn't work, then maybe we should close this issue and open a more general point about this problem. The general problem here is that the interface contract with a superclass is untyped and unchecked, and that's tripping up developers and it would be great if TS or the tooling can help.
@armandn I don't think our lack of closure or the niceness of our suggestion is what's keeping the request rejected, but the differences between C# and TS semantics:
C# base method override:
override
keywordC# interface implementation:
So the behavior in C# are quite different depending if you ask about classes or interfaces, but in any case you get the main three benefits:
With slightly different semantics, we already have 1) in TS, unfortunately 2) and 3) are missing. The reason override
was rejected, in my opinion, is that a similar syntax assumes a similar behavior, and this not desirable because:
A class with 5 methods, of which 3 are marked override, wouldn't imply that the other 2 aren't overrides.
It happens that we already have 1) 2) and 3) when writing object literals, but not when writing class members that extend other classes or implement interfaces.
With this in mind I think we can all agree on this semantics:
check
or super.
, or MyInterface.
)abstract/virtual
but checks for member existence in the base class / implemented interfaces. (Benefit 2)Additionally, I think this two are necessary for completeness of the solution*:
This two points are useful in the very-real use case of React component implementation:
class MyComponent<MyComponentProps, MyComponentState> {
implements.state = {/*auto-completion for MyComponentState here*/};
implements.componentWillMount(){
//do my stuff
}
}
The Annotation-based solution from @RyanCavanaugh is not enough because:
@override
. Given the semantics, it's just about choosing the right syntax. Here some alternatives:
override componentWillMount()
: Intuitive but misleading.check componentWillMount()
: Explicit but keyword-consuming. super componentWillMount()
: implements componentWillMount()
: super.componentWillMount()
: implements.componentWillMount()
:this.componentWillMount()
: ReactComponent.componentWillMount()
:Opinions?
@olmobrutall good summary. Couple of points:
One keyword option (taken from C#) would be new - indicating a new slot:
class MyComp extends React.Component<IProps,IState> {
...
new componentWillMount() { ... }
componentWillMount() { ...} // would compile, maybe unless strict mode is enabled
new componentwillmount() { ... } <-- error
My other point is the issue with mandatory nature of using the above. As a contract between the parent super class and the derived class it makes sense to specify what are interface points where the above syntax would be valid. These are really internal extensions points for the super class, so something like:
class Component<P,S> {
extendable componentWillMount() {...}
}
The same would apply to the interface too.
Thanks :)
What about just writing this
?
class MyComponent<MyComponentProps, MyComponentState> {
this.state = {/*auto-completion for MyComponentState here*/};
this.componentWillMount(){
//do my stuff
}
}
About extendable
, there's already abstract
in TS 1.6, and adding virtual will maybe create again the split world problem?
Right, I thought about abstract but there may be an implementation in the super class, so it doesn't really make sense. Likewise with virtual, as that would imply non-virtual members are not virtual - which is misleading.
this.
works, I guess you could even have (as a long form):
this.componentWillMount = () => { }
The only issue with this is that it should only be scoped to earmarked extension points, not all members of the base class.
what is going on...
TypeScript isn't and never has been the javascript version of C#. So the reason isn't because the suggested functionality differs from the semantics of C#. The reasons for closure as stated by Ryan and then clarified by Daniel R were
As Ryan explained, the issue is that marking a method as an override doesn't imply that another method isn't an override. The only way that it would make sense is if all overrides needed to be marked with an override keyword (which if we mandated, would be a breaking change).
Yet you're still persisting with your problems around autocomplete. Autocomplete doesn't need a new keyword in the language to provide you with improved suggestions.
The reason this thread exists was to get compiler errors when a function is illegally overridden or when a method was declared to be an override but didn't actually override a function in a base class. It's a simple and well defined concept across many languages that also support most if not more language features typescript has to offer. It doesn't need to solve all the worlds problems, it just needs to be the override keyword, for overrides.
More people have since shown interest in the original proposal, so lets stick to the topic the issue was raised for and raise new issues for new ideas.
TypeScript isn't and never has been the javascript version of C#.
I'm comparing it with C# as a reference language, since I assume this is the behavior you guys are assuming.
Autocomplete doesn't need a new keyword in the language to provide you with improved suggestions.
How you suggest it to be triggered then? If you show a autocomplete combobox when writing a random name in a class context will be very annoying when we just want to declare a new field or method.
The reason this thread exists was to get compiler errors when a function is illegally overridden or when a method was declared to be an override but didn't actually override a function in a base class.
I absolutely included this use case, under Benefit 2.
It doesn't need to solve all the worlds problems, it just needs to be the override keyword, for overrides.
So your suggestion is to fix one step at a time instead of moving one step back and look at similar/related problems?. That maybe a good idea for your Scrum Board but not for designing languages.
The reason they are conservative about adding the keyword is that they can not remove features from a language.
Some design mistakes in C# because of lack of completion:
var
only working for variable types, not for automatic generic parameters or return types. Mayble auto
will be a better keyword like in C++?Just try to use React for a second and you'll see the other side of the picture.
Overriding a method that's already been implemented in a base class and implementing an interface method are two completely different things. So yes, what's being suggested is to fix one of those scenarios with a dedicated keyword instead of trying to come up with some Swiss-Army keyword.
What good is a keyword that can mean any one of 3 different things to developers reading some code for the first time? It's ambiguous and confusing, especially if you're talking about using a keyword like this
which already does other (totally unrelated!) things in the language - it couldn't be more generic, it's almost useless.
If your main concern is auto-complete, editors have enough information now to be able to suggest methods from base classes and implemented interfaces 'as you type'.
Overriding a method that's already been implemented in a base class and implementing an interface method are two completely different things.
In the general case yes, but we're not talking about implementing any interface method. We're talking about an optional interface method where the parent class implements the interface. In this case you can say that 1) interface permits the method to be implemented as undefined
, 2) the parent class has an undefined implementation, and 3) the child class is overriding the undefined implementation with a method implementation.
@olmobrutall I think your comment about designing languages and how it is not a scrum board is a little self-serving. I have seen about four updates to TS in the space of under a year.
If the language design was as well considered as you imply it should be, then there would already be a language spec document telling us exactly how overrides should work, and we mightn't even be having this conversation.
I don't make this comment with any denigration of the TS developers/designers, because TS is already excellent and I would dread having to use standard JS instead.
Yes TS is not C# and it's not C++. But a number of languages have chosen the override keyword to meet the objectives discussed here so it seems counterproductive to suggest totally alien syntax.
The chief issue seems to be not wanting to introduce a breaking change. The simple answer is a compiler flag, end of story. For some people like me, an optional override keyword is useless. For others they want to embellish their code incrementally. Compiler flag solves the dilemma.
Signature differences are a different conversation. The new keyword appears unnecessary because JS can't support multiple methods of the same name (unless TS creates signature-derived mangled names a'la C++, which is highly unlikely).
I have seen about four updates to TS in the space of under a year.
I don't mean you can not be fast and iterate quickly. I'm as happy as anybody that ES6 and TS are evolving fast. What I mean is that you have to try to predict the future, to avoid putting the language in a dead end.
I could agree on using override
keyword. With proper arguments even keeping fields and interfaces out of the scope, but I can not agree with the 'let's stay focused and solve just this particular problem the way other languages do it without thinking too much' argument.
But a number of languages have chosen the override keyword to meet the objectives discussed here so it seems counterproductive to suggest totally alien syntax.
None of this languages have prototypal inheritance or optional method (methods that are neither abstract or virtual, they just could not exist at runtime), and this are related problems that have to be discussed (and maybe discarded) before making a commitment.
Put another way: let's say we do as you seem to suggest and we implement override without thinking to much. Then me, or anybody else using TSX, adds an issue with why override
doesn't work with React components. Whats your plan?
In the general case yes, but we're not talking about implementing any interface method. We're talking about an optional interface method where the parent class implements the interface.
It doesn't matter where the interface was set up, the fact is they are not the same thing and so shouldn't share a keyword because the intent of the program is not clear.
You could, for example, be overriding a method which had been implemented for interface compliance in a base class; If we were to put all our eggs in one keyword for these two different things, how would anyone then know if this was the initial declaration of that function or an override to one previously defined in a base class? You wouldn't, and it wouldn't be possible to know without further inspection of the base class - which may even be in a 3rd party .d.ts
file & thus making it an absolute nightmare to find, depending how deep in the inheritance chain the function was originally implemented.
Put another way: let's say we do as you seem to suggest and we implement override without thinking to much. Then me, or anybody else using TSX, adds an issue with why override doesn't work with React components. Whats your plan?
Why does this need to fix React? If React has a different problem to what this is trying to solve then I can't for the life of me fathom why override
needs to fix it? Have you tried opening another issue to suggest something be done about interface implementation?
I wouldn't agree that enough thought hasn't been put in to this. We're suggesting a tried and tested technique that's been successful in every other language I can think of that's implemented it.
the fact is they are not the same thing and so shouldn't share a keyword because the intent of the program is not clear.
Are not? Look at this two alternative definitions of BaseClass
class BaseClass {
abstract myMethod();
}
interface ISomeInterface {
myMethod?();
}
class BaseClass extends ISomeInterface {
}
And then in your code you do:
class ConcreteClass {
override myMethod() {
// Do stuff
}
}
You think it should work in just one case and not in the other? The effect is going to be 100% identical in Javascript (creating a new method in ConcreteClass prototype), from the external interface and from the tooling perspective.
Even more, maybe you want to capture this
inside of the method, implementing it with a lambda (useful for React event handling). In this case you'll write something like this:
class ConcreteClass {
override myMethod = () => {
// Do stuff
}
}
The behavior will be again identical if the method is abstract or comes from the interface: add a field in the class with a lambda implementing it. But it looks a little bit strange to override
a field, since you're just assigning a value.
Not let's see it using super.
(my favorite syntax right now, but I'm open to alternatives).
class ConcreteClass {
super.myMethod() { //method in prototype
// Do stuff
}
super.myMethod = () => { //method in lambda
// Do stuff
}
}
Now the concept is conceptually simpler: My super class says that there is a method or field and the ConcreteClass can define it in the prototype / assign it / read it / call it.
Why does this need to fix React?
Is not just react, look at angular:
Of course most of the interfaces are not meant to be implemented, neither all the classes to be overriden, but one thing is clear: in Typescript interfaces are more important than classes.
Have you tried opening another issue to suggest something be done about interface implementation?
How should I call it? override
for interface and fields?
We're suggesting a tried and tested technique that's been successful in every other language I can think of.
The languages you have in mind are quite different. They have inheritance based in a static VTable.
In Typescript a class is just an interface + automatic prototype inheritance of methods. And methods are just fields with a function inside.
In order to adapt the override
feature to TS this two fundamental differences have to be considered.
Please @kungfusheep make the effort to think about a solution to my problem. If you want to add different keywords and implement them in a second stage is O.K., but take a second to imagine how it should be.
I can't think of another way to say what I've already said. They are not the same. They are similar, but not the same. Please see this comment by one of the TS devs RE: the readonly keyword - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment-179563753 - which strengthens what I'm saying.
I agree with the general idea, but let's see in practice:
class MyComponent extends React.Component<{ prop : number }, { value: string; }> {
//assign a field defined in the base class without re-defining it (you want type-checking)
assign state = { value : number};
//optional method defined in an interface implemented by the base class
implement componentDidMount(){
}
//abstract method defined in the base class
override render(){
}
}
This looks like VB or Cobol, doesn't it?
It looks like it makes sense, at least.
Consider this example, were there only the override (or just one) keyword.
interface IDo {
do?() : void;
}
class Component implements IDo {
protected commitState() : void {
/// do something
}
override public do() : void {
/// base implements 'do' in this case
}
}
Now lets implement our own component using what we just wrote.
class MyComponent extends Component {
override protected commitState(){
/// do our own thing here
super.commitState();
}
override do() : void {
/// this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing.
}
}
One way of knowing would be the type of super
:
override do() : void {
super.do(); // this compiles, if it was an interface then super wouldn't support `do`
}
exactly! which means the design is wrong. there shouldn't be an investigation involved with skimming code, it should just make sense when you read it.
this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing.
What's the difference in practice? The object has just one space of names and you can only place one lambda in the do
field. There's no way of explicitly implementing an interface anyway. The implementation is going to be:
MyComponent.prototype.do = function (){
//your stuff
}
independently of what you write.
It doesn't matter what the output is. You could be either intentionally or unintentionally overriding some functionality in a base class, but there is no intent in a keyword that is ambiguous.
What error or unexpected behavior will be solved by having two keywords?
Come on now mate. You're obviously a clever guy. I've just said "unintentionally overriding some functionality in a base class"; can we not infer from this any unexpected behaviour that could have potentially just occurred?
To be clear, I'm not proposing turning this issue into a proposal for two keywords. This issue is for the override keyword - anything else will need a new proposal and its own discussion on semantics.
To be clear, I'm not proposing turning this issue into a proposal for two keywords. This issue is for the override keyword - anything else will need a new proposal and its own discussion on semantics.
It doesn't really matter in how many issues it should be discussed, or from whom the idea comes. You propose to split two very related thinks and don't even consider a consistent syntax.
The arguments for whether we need 1, 2 or 3 keywords belong to this thread and is not finished (...but becoming repetitive). Then maybe we can discuss the syntax in another thread (because the semantics are going to be identical anyway :P).
In my example:
class MyComponent extends React.Component<{ prop : number }, { value: string; }> {
//assign a field defined in the base class without re-defining it (you want type-checking)
assign state = { value : number};
//optional method defined in an interface implemented by the base class
implement componentDidMount(){
}
//abstract method defined in the base class
override render(){
}
}
Don't assign
, implement
and override
do exactly the same thing: Check that the name exist (in the base class, the implemented interfaces, the interfaces implemented by the base class, etc...).
If there's a name conflict between base classes and some implemented interfaces you'll get an compile-time error whether with 1, 2 or no keyword at all.
Also think about the object literal:
var mc = new MyComponent();
mc.state = null;
mc.componentDidMount =null;
mc.render = null;
With exactly the same syntax I can re-assign fields or method independently if they come from the base class, direct interface implementations or interfaces implemented in the base class.
Don't assign, implement and override do exactly the same thing: Check that the name exist (in the base class, the implemented interfaces, the interfaces implemented by the base class, etc...).
You've just described 3 different scenarios there, so obviously they're not the same. I have a feeling I could describe why they're different to you all day and you'd still be sat arguing that they're not, so I'm going to bow out of this particular line of discussion for now. There's nothing to say the TS guys are still considering this at the moment anyway.
With the closure of #6118 I think there's reason to discuss if the problems there and the problems here can be addressed simultaneously.
I was not aware of https://github.com/Microsoft/TypeScript/pull/6118. The idea looks like a possible alternative to adding override
.
If I understood the problem properly, the issue is that you can have more than one base classes / interfaces with compatible but not identical member declarations, and they have to be unified when being initialized in the child class without a type.
Without having a good idea of the consequences, I would be happy if:
More important IMO would be to have some way of triggering auto-completion when writing class members (Ctrl+Space). When the cursor is directly inside of a class, you could be defining new members of re-defining inherited ones, so the auto-completion can not be too aggressive, but with manual triggering the behavior should be OK.
Regarding @RyanCavanaugh comments:
We definitely understand the use cases here. The problem is that adding it at this stage in the language adds more confusion than it removes. A class with 5 methods, of which 3 are marked override, wouldn't imply that the other 2 aren't overrides. To justify its existence, the modifier would really need to divide the world more cleanly than that.
Typing a variable as any
doesn't imply that some other variable is or isn't also any
. But, there is a compiler flat --no-implicit-any
to enforce that we explicitly declare it. We could equally have a --no-implicit-override
, which I for one would turn on if it were available.
Having an override
keyword that is used gives developers an enormous amount of insight when reading code that they are unfamiliar with, and the ability to enforce it would give some additional compile time control.
For all the +1ers -- can you talk about what's not sufficient about the decorator solution shown above?
Is there any way in which a decorator a better solution than an override keyword? There are many reasons why it is worse: 1) It adds runtime overhead however small; 2) It's not a compile time check; 3) I have to include this code to every one of my libraries; 4) There is not way for this to catch functions that are missing the override keyword.
Let's give an example. I have a library with three classes ChildA
, ChildB
, Base
. I've added some method doSomething()
to ChildA
and ChildB
. After some refactoring, I've added some additional logic, optimized, and moved doSomething()
to the Base
class. Meanwhile, I have another project which depends on my library. There I have ChildC
with doSomething()
. There is no way when I update my dependencies to find out that ChildC
is now implicitly overriding doSomething()
, but in an unoptimized way that is also missing some checks. That is why an @overrides
decorator will never be sufficient.
What we need is an override
keyword, and a --no-implicit-override
compiler flag.
An override
keyword would help me a lot since I use in my project a simple hierarchy of base classes to create all my components. My problem lies in the fact that these components may need to declare a method to be used somewhere else, and this method may or may not be defined in a parent class and may or may not already do stuff that I need.
For example, let's say that a function validate
takes a class with a getValue()
method as parameter. To build this class, I can inherit another class that could already define this getValue()
method, but I can't really know this unless I look at its source code. What I'd instinctively do is implement this method in my own class, and as long as the signature is correct no one will tell me anything.
But maybe I shouldn't have done that. The following possibilities all suppose that I did an implicit override:
super
in my override, but no one suggested me to do so. Having a mandatory override keyword would have told me "hey, you're overriding, so maybe you should check what the original method looks like before doing your stuff". That would greatly improve my experience with inheritance.
Of course, as suggested, it should be placed under a --no-implicit-override
flag since it would be a breaking change and that most people do not care that much about all this.
I like the comparaison @eggers made with any
and --no-implicit-any
because it's the same kind of annotation and it would work in the exact same way.
@olmobrutall I was looking at some of your talk about overriding abstract methods & interface methods.
If my opinion, override
implies the existence of a super.
Neither abstract methods nor methods defined in an interface can be called via a super.
call, and so should not be overrideable (there is nothing to override). Instead if we do something more explicit for those cases, it should be an implements
keyword. But, that would be a separate feature discussion.
Here are the problems as I see them, ranked most important to least. Did I miss anything?
It's easy to think you're overriding a base class method when you're not:
class Base {
hasFilename(f: string) { return true; }
}
class Derived extends Base {
// oops
hasFileName(f: string) { return false; }
}
This is probably the biggest problem.
This is very closely related, especially when the implemented interface has optional properties:
interface NeatMethods {
hasFilename?(f: string): boolean;
}
class Mine implements NeatMethods {
// oops
hasFileName(f: string) { return false; }
}
This is a less important problem than failure to override, but it's still bad.
It's possible to override a base class method without realizing it
class Base {
hasFilename(f: string) { return true; }
}
class Derived extends Base {
// I didn't know there was a base method with this name, so oops?
hasFilename(f: string) { return true; }
}
This should be relatively rare just because the space of possible method names is very large, and the odds that you'd also write a compatible signature and not mean to have been doing this in the first place are low.
any
sIt's easy to think that override methods will get the parameter types of their base:
class Base {
hasFilename(f: string) { return true; }
}
class Derived extends Base {
// oops
hasFilename(f) { return f.lentgh > 0; }
}
We tried to type these parameters automatically but ran into problems. If hasFilename
were explicitly marked override
we might be able to solve those problems more easily.
It's not immediately clear when a method is overriding a base class method and when it's not. This seems like a smaller problem because there are reasonable workarounds available today (comments, decorators).
You have to manually type out base method names when writing derived class override methods, which is annoying. We could easily fix this by always providing those names in the completion list (I think we actually have a bug on this already), so we don't really need a language feature to fix this problem.
Listing these out as things that either belong in a separate issue or simply aren't going to happen:
override
, and it really does have undesirable semantics in many good scenariosimplements.foo = ...
). No need to bikeshed here@RyanCavanaugh I agree with all of the above in about that order as well. Just as a comment, I don't think that the override
keyword should be solving the "failure to implement" problem. As I said above, I think that problem should be solved by a different keyword (e.g. implements
) as part of a different ticket. (override
should imply a super.
method)
@RyanCavanaugh I agree with all the points but I don't see any reference to the also very related problem of initialized fields declared in a parent type without overriding the type (and then loosing type checking). I think the feature should not be confined to method declarations in a language where methods are just functions in object fields.
@eggers even if at the end the end two keywords are needed, the semantics will be very similar and I think the features should be discussed together.
override should imply a super. method
In C# override can (and must) be used also for abstract methods (with no .super). In Java @override is an attribute. Of course they are different language but with similar keywords you expect similar behaviors.
I agree with @RyanCavanaugh 's points, though I'd say the "accidental override" problem might be a little more common than he believes (especially when dealing with extending a class that already extends something else, like a React component that would already define a componentWillMount
method in the first extension).
I believe though, like @eggers said, that the "failure to implement" issue is something pretty different, and it would feel weird to use an override
keyword for a method that doesn't exist in a parent class. I know these are different languages with different problematics, but in C# override
isn't used for interfaces. I would suggest, if we could get a --no-implicit-override
flag, that interface methods would have to be prefixed with the interface name (which would look a lot like C# and thus feel more natural).
Something like
interface IBase {
method1?(): void
}
class Base {
method2() { return true; }
}
class Test extends Base implements IBase {
IBase.method1() { }
override method2() { return true; }
}
I think @RyanCavanaugh lists the fundamental problems. I would complement that with "What are the disagreements":
override
? For example: interface IDrawable
{
draw?( centerPoint: Point ): void;
}
class Square implements IDrawable
{
draw( centerPoint: Point ): void; // is this an override?
}
override
? interface IPoint2
{
x?: number;
y?: number;
}
class Circle implements IPoint2
{
x: number; // is this an override?
y: number; // is this an override?
radius: number;
}
implements
keyword, to handle the above cases instead of an override
keyword, and the form it would take.@kevmeister68 thanks for explicitly stating the disagreements.
One thing: you have to make the interface members optional to really show the problem, like this the typescript compiler will complain when a field or method is not implemented, solving "Failure to implement".
@olmobrutall Thanks for that. I've never declared a method optional - can it be (I come from a C# background and there is no such concept)? I've updated the examples I listed above to change the member variables to optional - is that better?
@kevmeister68 also the draw
method in IDrawable
:)
@RyanCavanaugh
Insufficiently exotic syntax (e.g. implements.foo = ...). No need to bikeshed here
Thanks for teaching me a new expression. I don't see a lot of problems in the semantics of the feature:
Most of the problems rely on the syntax and the behaviours that they impliy:
Let's compare the tree more promising syntaxes:
class Person{
dateOfBirth: Date;
abstract talk();
walk(){ //...}
}
interface ICanFly{
fly?();
altitude?: number;
}
class SuperMan extends Person implements ICanFly {
dateOfBirth = new Date(); //what goes here?
override talk(){/*...*/}
walk = () => {/* force 'this' to be captured*/} //what goes here
implements fly() {/*...*/}
altitude = 1000; //what goes here?
}
Advantages:
extends
/ implements
. Disadvantages:
override
or implements
feels right. Maybe super.
, but then what we do with interfaces?function()
the same problems happens.InterfaceName.
class Person{
dateOfBirth: Date;
abstract talk();
walk(){ //...}
}
interface ICanFly{
fly?();
altitude?: number;
}
class SuperMan extends Person implements ICanFly {
dateOfBirth = new Date(); //what goes here?
override talk(){/*...*/}
walk = () => {/* force 'this' to be captured*/} //what goes here
ICanFly.fly() {/*...*/}
ICanFly.altitude = 1000; //what goes here?
}
Advantages:
super.
for them. Disadvantages:
this.
class Person{
dateOfBirth: Date;
abstract talk();
walk(){ //...}
}
interface ICanFly{
fly?();
altitude?: number;
}
class SuperMan extends Person implements ICanFly {
this.dateOfBirth = new Date();
this.talk(){/*...*/}
this.walk = () => {/* force 'this' to be captured*/}
this.fly() {/*...*/}
this.altitude = 1000;
}
Advantages:
this.
to trigger auto-completion feels natural. Disadvantages:
Whilst failure to implement a non-optional interface method will cause a compiler error, if the interface changes at some point in the future (lets say a method is removed) there will be no warning for all classes that implemented that method. This might not be as big an issue as optional's but I think it's fair to say the scope of this goes beyond them.
I do however, still believe override
is a distinctly different thing and potentially having two keywords rather than one would better convey the intent of the program.
For example, consider a scenario where a developer believes they are implementing an interface method when in-fact they're overriding the method which has already been declared in a base class. With two keywords the compiler can prevent this kind of mistake and throw an error to the tone of method already implemented in a base class
.
interface IDelegate {
execute?() : void;
}
class Base implements IDelegate {
implement public execute() : void { /// fine, this is correctly implementing execute
}
}
class Derived extends Base {
implement public execute() : void {
/// ERROR: `method "execute():void" already implemented in a base class`
}
}
I don't know if this is relevant in JS, but class fields are supposed to be private in OO, so I don't think we need to be overriding fields explicitly since the fact that they're private already prevent us to override altogether.
Instance methods are fields though, and from what I gather a lot of people use them instead of prototype methods. About that,
class Person{
walk(){ //...}
}
class SuperMan extends Person {
walk = () => {/* force 'this' to be captured*/}
}
is a compiler error, because walk
is a prototype method in Person
and an instance method in SuperMan
.
Anyway, i'm not sure override
would be a fit here, because well, we don't override fields. Again, it would look like C#, but I'd rather use the new
keyword instead of override
here. Because it's not the same thing as a method override (I can call super.myMethod
in an override, not here).
My preferred solution would then be something like (assuming we're in strict override mode):
class Person{
dateOfBirth: Date;
talk() { }
walk = () => { }
}
interface ICanFly {
fly?();
altitude?: number;
}
class SuperMan extends Person implements ICanFly {
new dateOfBirth = new Date();
override talk() { }
new walk = () => { }
implements fly() {/*...*/}
implements altitude = 1000;
}
My main concern is about interfaces though. We shouldn't have to write implements
for non optional interface members, because we'll be already caught by the compiler. Then, if we do so, there'll be a confusion between what is from an interface and what isn't, since not all interfaces members would be prefixed by implements
. And that word is a mouthful. I'm not ready to write something like this:
class C extends React.Component {
implements componentWillMount() { }
implements componentDidMount() { }
implements componentWillReceiveProps(props) { }
/// ... and the list goes on
}
I proposed to prefix with the interface name earlier but @olmobrutall showed me that it was a worse idea.
Anyway, I'm pretty convinced that we'll need 3 different new keywords to properly tackle this.
I also don't think that it's necessary to implicitly type the overrides since the compiler will already prevent us to write something that's not compatible, especially because it is apparently difficult to do right.
Isn't new
, override
and implement
too much keyword overhead for essentially the same JS semantics? Even if in C# they are different things there is virtual no difference in JS/TS.
It's also quite ambiguous:
new
for classes but implements
for interfaces? override
, implements
, any of the two or both at the same time? Also think about this:
class Animal {
}
class Human extends Animal {
}
class Habitat {
owner: Animal;
}
class House extends Habitat {
owner = new Human();
}
var house = new House();
house.owner = new Dog(); //Should this be allowed??
The question is:
Does owner = new Human();
re-define the field with a new (but compatible) type, or just assigns a value.
I think in general you re-defined the field except if you using the magic keyword. My preference is for this.
right now.
class House extends Habitat {
this.owner = new Human(); //just setting a value, the type is still Animal
}
@olmobrutall That might indeed be a bit much keyword overhead, but all 3 are indeed different things.
override
would apply to methods (defined on the prototype), meaning that it does not erase the original method, which is still accessible behind super
.new
would apply to fields, and mean that the original field has been erased by the new one.implements
would apply to anything from an interface, and mean that it does not erase or replace anything that already existed in a parent class, because an interface "doesn't exist".If you're changing a method from a base class that was implemented with an interface what should you use? I can see four possibilities: override , implements , any of the two or both at the same time?
It seems pretty logical to me that it would be override
since that what you're effectively doing here. implements
would mean that you're implementing something from the interface that doesn't exist otherwise. In a sense, override
and new
would have priority over implements
.
And about your field override exemple, nothing should change about how it works today. I'm not sure what happens here, but whatever it is it shouldn't change (or maybe it should, but that's not what we're discussing here).
I feel that override
would be suitable for both base methods and properties, including abstract ones (explained below).
new
is an interesting idea in concept, but this particular keyword may be confused with class instantiation, so it may give the false impression it would only work for classes, despite the fact that properties can have primitive, interface, union or even function types. Perhaps a different keyword like reassign
could work better there, but it has the problem that it may give a false idea in the case the value is only re-declared but not actually assigned with anything in the derived class (new
may have that problem as well). I think redefine
is also interesting, but could lead to the mistaken expectation that the 'redefined' property could also have a different type than the base one, so I'm not sure.. (edit: actually I checked and it could have a different type as long as the new one is a subtype of the base one, so this may not be that bad).
implement
(I prefer this particular verb form for consistency with override
) seems to work for interfaces. I believe it could also technically work for abstract base methods as well, but using override
could feel more consistent, despite the slightly different semantics. Another reason is that it would make it a bit inconvenient to change a method from abstract to non-abstract, as one would need and go to all the derived classes and change override
to implement
.
There could be better ideas, but that's all I have at this point..
I proposed the keyword new
because it's the one C# uses for this exact feature, but I agree this isn't the best one. implement
is indeed a better choice over implements
. I don't think we should use it for abstract methods since they're part of a base class and not an interface. override
+ new
-> base class, implement
-> interface. That seems clearer.
@JabX
I've checked and you're right:
class Person{
walk(){ //...}
}
class SuperMan extends Person {
walk = () => {/* force 'this' to be captured*/}
}
Fails with Compiler error: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.
I don't really see the reason to fail in this case, since the parent type contract is fulfilled and you can write this anyway:
var p = new SuperMan();
p.walk = () => { };
Or even
class SuperMan extends Person {
constructor() {
super();
this.walk = () => { };
}
}
override
override would apply to methods (defined on the prototype), meaning that it does not erase the original method, which is still accessible behind super.
That's actually an argument. There is least some observable difference between override
and implements
... but not quite.
Both override
and implements
are implemented in the prototype
, being able to use super
is independent, because you call super.walk()
not just super()
, and is available in every method ( overrides, implements, news and normal definitions).
class SuperMan extends Person implements ICanFly {
new dateOfBirth = new Date();
override talk() { } //goes to prototype
new walk = () => { }
implements fly() {/*...*/} //also goes to the prototype
implements altitude = 1000;
}
And being able to use super.walk()
also works if you assign to the instance
class SuperMan extends Person {
constructor() {
super();
this.walk = () => { super.walk(); };
}
//or with the this. syntax
this.walk = () => { super.walk(); };
}
There's already a clear way to differentiate prototype
fields from instance fields and is the =
token.
class SuperMan extends Person implements ICanFly {
this.dateOfBirth = new Date(); //instance
this.talk() { } //prototype
this.walk = () => { } //instance
this.fly() {/*...*/} //prototype
this.altitude = 1000; //instance
}
With the this.
syntax the problem is solved more elegantly:
this.
means check my type (base classes and implemented interfaces).=
means assign to the instance instead of the prototype. New
new
would apply to fields, and mean that the original field has been erased by the new one.
Take into account that you can not change the field or method with a different type because you will break the type system. In C# or Java when you do new
the new method will be used only on statically dispatched calls to the new type. In Javascript all the calls will be dynamic and if you change the type the code will likely break at run-time (Coercing the type could be allowed for practical purposes however, but not widening).
class Person {
walk() { }
run() {
this.walk();
this.walk();
this.walk();
}
}
class SuperMan extends Person {
new walk(destination: string) { } //Even if you write `new` this code will break
}
So more than new
the keyword should be assign
, because this is the only type-safe thing you can do:
class Person{
dateOfBirth: Date;
}
class SuperMan extends Person implements ICanFly {
assign dateOfBirth = new Date();
}
Note that reassign
doesn't make sense, because Person only declares the field but doesn't set any value on it.
Writing assign
seems redundant however, it's clear that we're assigning because we have a =
at the other side.
Again the this.
syntax solves the problem gracefully:
class Person{
dateOfBirth: Date;
}
class SuperMan extends Person implements ICanFly {
this.dateOfBirth = new Date();
}
(Update by @RyanCavanuagh)
Please see this comment before asking for "any updates", "please add this now", etc.. Comments not meaningfully adding to the discussion will be removed to keep the thread length somewhat reasonable.
(NOTE, this is not a duplicate of Issue #1524. The proposal here is more along the lines of the C++ override specifier, which makes much more sense for typescript)
An override keyword would be immensely useful in typescript. It would be an optional keyword on any method that overrides a super class method, and similar to the override specifier in C++ would indicate an intent that "the name+signature of this method should always match the name+signature of a super class method". This catches a whole range of issues in larger code bases that can otherwise be easily missed.
Again similar to C++, it is not an error to omit the override keyword from an overridden method. In this case the compiler just acts exactly as it currently does, and skips the extra compile time checks associated with the override keyword. This allows for the more complex untyped javascript scenarios where the derived class override does not exactly match the signature of the base class.
Example use
Example compile error conditions
IntelliSense
As well as additional compile time validation, the override keyword provides a mechanism for typescript intellisense to easily display and select available super methods, where the intent is to specifically override one of them in a derived class. Currently this is very clunky and involves browsing through the super class chain, finding the method you want to override, and then copy pasting it in to the derived class to guarantee the signatures match.
Proposed IntelliSense mechanism
Within a class declaration: