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

Support final classes (non-subclassable) #8306

Closed
Zorgatone opened this Issue Apr 26, 2016 · 98 comments

Comments

Projects
None yet
@Zorgatone
Copy link

Zorgatone commented Apr 26, 2016

I was thinking it could be useful to have a way to specify that a class should not be subclassed, so that the compiler would warn the user on compilation if it sees another class extending the original one.

On Java a class marked with final cannot be extended, so with the same keyword on TypeScript it would look like this:

final class foo {
    constructor() {
    }
}

class bar extends foo { // Error: foo is final and cannot be extended
    constructor() {
        super();
    }
}
@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented Apr 26, 2016

a class with private constructor is not extendable. consider using this instead.

@Zorgatone

This comment has been minimized.

Copy link
Author

Zorgatone commented Apr 26, 2016

From what I recalled I was sure the compiler didn't like the private keyword on the constructor. Maybe I'm not using the paste version though

@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented Apr 26, 2016

This is a new feature, will be released in TS 2.0, but you can try it using typescript@next. see #6885 for more details.

@Zorgatone

This comment has been minimized.

Copy link
Author

Zorgatone commented Apr 26, 2016

Ok thank you

@duanyao

This comment has been minimized.

Copy link

duanyao commented Apr 27, 2016

Doesn't private constructor also make a class not instantiatable out of the class? It's not a right answer to final class.

@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented May 17, 2016

Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support. In TypeScript there is nothing we can offer to make your code run any better than it did without final.
Consider using comments to inform your users of the correct use of the class, and/or not exposing the classes you intend to be final, and expose their interfaces instead.

@0815fox

This comment has been minimized.

Copy link

0815fox commented Jun 20, 2016

I do not agree with that, instead I agree with duanyao. Private does not solve that issue, because I also want classes which are final to be instanciateable using a constructor. Also not exposing them to the user would force me to write additional factories for them. For me the main value of final support is, that it prevents users from making mistakes.
Arguing like that: What does TypeScript offer to make my code run faster, when I use types in function signatures? Isn't it also only for preventing users from making mistakes? I could write comments describing which types of values a user should pass in as a parameter. It's a pitty, that such extensions like a final keyword are just pushed away, because on my opinion it collides with the original intension of typescript: make JavaScript safer by adding a compilation level, which performs as many checks as possible to avoid as many mistakes upfront as possible. Or did I misunderstand the intention of TypeScript?

@0815fox

This comment has been minimized.

Copy link

0815fox commented Jun 20, 2016

There should also be a final modifier for methods:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

E.g. I often use following pattern, where I want to urgently avoid fooIt to be overridden:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}
@mhegazy

This comment has been minimized.

Copy link
Contributor

mhegazy commented Jun 20, 2016

The argument about cost vs. utility is a fairly subjective one. The main concern is every new feature, construct, or keyword adds complexity to the language and the compiler/tools implementation. What we try to do in the language design meetings is to understand the trade offs, and only add new features when the added value out weights the complexity introduced.

The issue is not locked to allow members of the community to continue adding feedback. With enough feedback and compelling use cases, issues can be reopened.

@mindarelus

This comment has been minimized.

Copy link

mindarelus commented Aug 7, 2016

Actually final is very simple concept, does not add any complexity to the language and it should be added. At least for methods. It adds value, when a lot of people work on a big project, it is valuable not to allow someone to override methods, that shouldn't be overridden.

@mcdirmid

This comment has been minimized.

Copy link

mcdirmid commented Sep 9, 2016

In TypeScript there is nothing we can offer to make your code run any better than it did without final.

Wow, cringe! Static types don't make your code run any better either, but safety is a nice thing to have.

Final (sealed) is right up there with override as features I'd like to see to make class customizations a bit safer. I don't care about performance.

@pauldraper

This comment has been minimized.

Copy link

pauldraper commented Oct 10, 2016

Static types don't make your code run any better either, but safety is a nice thing to have.

Exactly. Just as private prvents others from calling the method, final limits others from overriding the method.

Both are part of the class's OO interface with the outside world.

@timmeeuwissen

This comment has been minimized.

Copy link

timmeeuwissen commented Oct 13, 2016

Completely agree with @pauldraper and @mindarelus. Please implement this, this would make a lot of sense I really miss it currently.

@aluanhaddad

This comment has been minimized.

Copy link
Contributor

aluanhaddad commented Oct 13, 2016

I don't think final is only beneficial for performance, it's also beneficial for design but I don't think it makes sense in TypeScript at all. I think this is better solved by tracking the mutability effects of Object.freeze and Object.seal.

@0815fox

This comment has been minimized.

Copy link

0815fox commented Oct 24, 2016

@aluanhaddad Can you explain that in more detail? Why do you think it does not "make sense in TypeScript at all"?
Freezing or sealing object means to disallow adding new properties to an object, but does not prevent adding properties to a derived object, so even if I would seal the base class I could still override the method in a child class, which extends that base class. Plus I could not add any properties to the base class at runtime.

@hk0i

This comment has been minimized.

Copy link

hk0i commented Oct 29, 2016

The idea of using final on a class or class method in java has more to do with minimizing mutability of the object for thread safety in my opinion. (Item 15. Joshua Bloch, Effective Java)

I don't know if these principals carry over into javascript seeing as everything in JS is mutable (correct me if I'm wrong). But Typescript is not Javascript, yeah?

I would really like to see this implemented. I think it'll help create more robust code. Now... How that translates into JS, it honestly probably doesn't have to. It can just stay on the typescript side of the fence where the rest of our compile-time checking is.

Sure I can live without it, but that's part of what typescript is, right? Double checking our overlooked mistakes?

@rylphs

This comment has been minimized.

Copy link

rylphs commented Nov 1, 2016

To me final would play the same role in typescript as private or typings, that is code contract. They can be used to ensure your code contract don't get broken. I would like it so much.

@cloverich

This comment has been minimized.

Copy link

cloverich commented Dec 15, 2016

@hk0i its also mentioned in Item 17 (2nd edition) in a manner similar to what's been echoed here:

But what about ordinary concrete classes? Traditionally, they are neither final nor designed and documented for subclassing, but this state of affairs is danger- ous. Each time a change is made in such a class, there is a chance that client classes that extend the class will break. This is not just a theoretical problem. It is not uncommon to receive subclassing-related bug reports after modifying the internals of a nonfinal concrete class that was not designed and documented for inheritance.

The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed. There are two ways to prohibit subclassing. The easier of the two is to declare the class final. The alternative is to make all the constructors private or package-private and to add public static factories in place of the constructors.

I would argue it does not increase the cognitive complexity of the language given that the abstract keyword already exists. However, I cannot speak to the implementation / performance impact of it and absolutely respect protecting the language from that angle. I think separating those concerns would be fruitful towards deciding whether or not to implement this feature.

@MrDesjardins

This comment has been minimized.

Copy link

MrDesjardins commented Mar 30, 2017

I believe that final would be an excellent addition to seal a class. One use case is that you may have a lot of public methods in your class but expose, through an interface, just a subset of them. You can unit tests the implementation quickly since it has all these public methods while the real consumer uses the interface to limits access to them. Being able to seal the implementation would ensure that no one extends the implementation or change public methods.

You may also ensure that no one is inheriting your class. TypeScript should be there to enforce those rules, and the suggestion about commenting seems to be a lazy approach to solve this use case. The other answer I read is about using private which is only suitable for a particular situation but not the one I explained above.

Like many people in this thread, I would vote to be able to seal class.

@kerberjg

This comment has been minimized.

Copy link

kerberjg commented Jul 4, 2018

@pelotom If you don't want to use OOP constructs and patterns, just don't, no one's telling you to do otherwise, however the same should apply in the opposite direction: if someone wants to use OOP constructs, just let them.
By forcing others to adopt a specific (non-OOP) pattern, you're adopting the same behaviour as the one you denounce OOP for. At least be consistent :)

Adding a final keyword to TypeScript is not a breaking change and won't influence any of your existing projects in any way, however it will be a vital feature for people such as @thorek, myself, and many others.

@pelotom

This comment has been minimized.

Copy link

pelotom commented Jul 4, 2018

if someone wants to use OOP constructs, just let them.

We’re not debating whether people should use OOP constructs, we’re debating whether currently non-existent OOP constructs should be added to the language. Doing so takes developer time that could be spent on other features, and makes the language more complex, which slows the rate of future features (because for every new feature it must be considered how it will interact with every old feature). So it certainly does affect me, because I’d rather spend TypeScript’s development and complexity budget on more useful things!

@iteriani

This comment has been minimized.

Copy link

iteriani commented Jul 19, 2018

fwiw, we're probably going to encourage our customers (google) to use @Final in comments so it propagates to closure compiler :\

@dimiVergos

This comment has been minimized.

Copy link

dimiVergos commented Jul 30, 2018

Adding final on classes and methods is useful when an R&D team builds TS libraries that other teams can use. These keywords are (an important) part of service exposure stability of such tools.
The keyword is more important for large teams and less important for small projects, imho.

@kerberjg

This comment has been minimized.

Copy link

kerberjg commented Jul 30, 2018

@dimiVergos This is exactly my case, and I assume the same for many others. I can imagine I may seem biased due to my position, but it seems the only (albeit highly) justifiable use of this keyword according to my experience.

I'm open to corrections on this one!

@cbutterfield

This comment has been minimized.

Copy link

cbutterfield commented Aug 7, 2018

I care more about supporting final methods (to avoid subclasses accidentally overriding them) than I do final classes. Is the the right ticket to vote for that? Ticket #9264 seems to imply so.

So how do I vote for "final methods"? Or did I just vote for it by this comment? Also how do we add "methods" to the title? Can I just edit it? The current title is misleadingly limited solely to classes but the discussion includes methods too.

@tomfumb

This comment has been minimized.

Copy link

tomfumb commented Sep 4, 2018

Like many others I would like to see final methods in TypeScript. I've used the following approach as a partial workaround that is not immune to failure but at least helps a little:

class parent {
  public get finalMethod(): () => void {
    return () => {
      // final logic
    };
  }
}

class child extends parent {
  // ERROR "Class 'parent' defines instance member accessor 'finalMethod', but extended class 'child' defines it as instance member function"
  public finalMethod(): void { }
}
@ChuckJonas

This comment has been minimized.

Copy link

ChuckJonas commented Sep 25, 2018

Here's another real world use case for final methods. Consider the following extension of a React.Component to make it easier to implement a simple Context.

interface FooStore{
  foo: string;
}

const {Provider, Consumer} = React.createContext<FooStore>({
  foo: 'bar';
});

abstract class FooConsumerComponent<P, S> extends React.Component<P, S> {

  // implement this instead of render
  public abstract renderWithStore(store: FooStore): JSX.Element;

  public final render() {
    return (
      <Consumer>
        {
          (store) => {
            return this.renderWithStore(store);
          }
        }
      </Consumer>
    );
  }
}

Without final on the render() method, nothing stops one from incorrectly overriding the method, thus breaking everything.

@yd021976

This comment has been minimized.

Copy link

yd021976 commented Oct 12, 2018

Hi, like many others I would like "final" or "sealed" keyword at least for methods. I totally agree that this keyword is a really added value as it allow developper to prevent their librairies customer to extend crucial code blocks (like in plugin for example).

The only workaround I found is to use method decorator like :
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

then in your "base" class, use decorator to prevent method override, example :

class MyBaseClass {
@sealed
public MySealedMethod(){}

public OtherMethod(){}

...
}

This way 'MySealedMethod' couldn't (easily) be overriden in sub class. Exemple:

class AnotherClass extends MyBaseClass {
public MySealedMethod(){} ====>> ERROR at RUNTIME (not at compile time)
}

actually, the only downside of this workaround is that the "sealed" is only visible at runtime (error in javascript console) but NOT at compile time (as the method properties are set via function I guess)

@asimonf

This comment has been minimized.

Copy link

asimonf commented Nov 20, 2018

@RyanCavanaugh

Yes. Every time we add a keyword, "but then we'll have to add this other keyword too!" is a strong point against adding it, because we want to grow the language as carefully as possible.

It seems to me that you're starting this argument with your mind set up against the feature. A popular feature should not be a point against considering such feature. It should be a point towards asking yourself why it's popular, and then carefully consider it.

I'm sure you've discussed it and arguments have been flung around towards considering it or against it. Is the argument against it just that it's feature creep, or is there anything more specific? Do you consider it a fringe feature? If so, could we do something to change your mind?

Let me just leave these questions here for all of us to ponder.

Is there a way to seal a class by design using present constructs?
If the feature was added, would it allow us to do anything new? (As in, something present constructs don't permit)
If the feature did allow us to do something new, is it something valuable?
Would adding sealed classes increase language complexity too much?
Would adding sealed classes increase compiler complexity too much?

For the purpose of discussing these, I'm not including sealed methods in the discussion and I'm also not including run-time checks in the implementation. We all understand we can't enforce this on Javascript.

FYI: I'd be awesome if the counter to this feature was a bit more than just blanket statements against feature-creep.

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Nov 21, 2018

@asimonf I think everything you've said was already addressed by this comment (you will need to expand the "show more" section to see it) and the one following it.

What would change our mind here? People showing up with examples of code that didn't work the way it should have because the sealed keyword was missing.

The scenario here that the keyword attempts to address is one where someone mistakenly inherits from a class. How do you mistakenly inherit from a class? It's not an easy mistake to make. JavaScript classes are inherently fragile enough that any subclass must understand its base class's behavioral requirements, which does mean writing some form of documentation, and the first line of that documentation can simply be "Do not subclass this class". Or there can be a runtime check. Or the class constructor can be hidden behind a factory method. Or any other number of options.

Why must TypeScript have this keyword when the situation is already one in which you should have had encountered at least two separate roadblocks telling you not to be there to begin with?

I've been programming for approximately forever years now and I have not once tried to subclass something only to find out it was sealed and I shouldn't have tried. It's not because I'm a super genius! It's just an extremely uncommon mistake to make, and in JavaScript these situations are already ones where you need to be asking questions of the base class in the first place. So what problem is being solved?

@Xample

This comment has been minimized.

Copy link

Xample commented Nov 21, 2018

A real case I would be using final is to prevent overriding a method with something which would never call the super method. Some public method of course. But the real solution I figured out would be the ability to force any sub method to call the super one. #21388

@dudewad

This comment has been minimized.

Copy link

dudewad commented Dec 14, 2018

As I'm not a language designer, I can't speak to the drawbacks of adding a keyword to the language. I will say that I am currently working on an abstract superclass that has a method I intend to be final, but to my dismay found that I couldn't make it so. For now I will go with the less-than-ideal approach of putting a comment, but team members implementing the class might not read said comment.

I think that above there are plenty of excellent examples of when/where final would be not only useful, but provide real value and real safety to the platform being built. It's no different for me. As I said, I don't know much about language design complexities but this is clearly a feature that people want/will use/I can't really see a way to abuse it or that it would generate bad code. I understand the concept of fearing scope creep but TS developers don't have to implement everything the community wants.

Just to add my bit to the debate, I would like to put my thumb on the yes-side of the scale because this issue is replete with good arguments supporting the addition of the feature, yet the arguments against that I see are:

  • It could lead to scope creep (this is manageable)
  • People will ask for other features (okay, but who cares, that's part of designing a language and this isn't legal precedent we're setting)
  • It could reduce efficiency (I imagine that since the TS team is full of bad-asses you'd probably find a way to make this manageable)
  • Opinionated "Don't use OOP concepts" comments

None of the above seem like credible threats to the TS ecosystem itself, nor the code that is produced out of it; they all seem political (except maybe the efficiency one, but I have huge confidence in you guys so I'm not really worried about it, plus a minor efficiency decrease would be worth not blowing up important component/library constructs by unwitting inheritance mistakes).

Hope this is constructive! Love TS and want to see it keep improving!

👍

--edit--

In response to @RyanCavanaugh:

So what problem is being solved?

The problem being solved I think is outlined by many, many comments above. I don't mean this as an insult but that question does make it sound, to me, like you aren't actually paying attention to the arguments presented, and that you have your mind set on "no". I don't mean disrespect with that, its just that, honestly, this thread is full of examples of problems that this would solve.

Somehow I missed this comment, which very well outlines a serious concern for adding the feature and that makes sense to me. Again, I don't know the complexity of adding it but efficiency concerns aside (since it's not up to developers to worry about TS efficiency, that's the job of the TS team), there don't seem to be any good arguments against final (in my humblest of opinions).

@cbutterfield

This comment has been minimized.

Copy link

cbutterfield commented Dec 14, 2018

Most of the recent discussion has been centered on final methods (less so on final classes). Final methods are certainly my primary interest. I just noticed that if you expand the "this comment" link in @RyanCavanaugh 's previous post that he considers the final method discussion off-topic. However @mhegazy suggested that this ticket is the right place (when closing #9264). So as a lowly end-user I'm baffled as to where I should be asking for/voting for final methods. Guidance would be appreciated.

@dudewad

This comment has been minimized.

Copy link

dudewad commented Dec 15, 2018

@cbutterfield
The contradiction is something that I noticed as well but did not point out in my post for fear of ranting (I actually found THIS thread through #9264 and came here at @mhegazy's direction).
@RyanCavanaugh
@mhegazy
Per the comment above, where is the correct place to discuss final methods, because that actually is the thing I'm most interested in.

@pelotom

This comment has been minimized.

Copy link

pelotom commented Dec 15, 2018

Looking back on my heavily downvoted comment (make sure to go add your 👎 if you haven't yet!) I'm happy to report that React is no longer setting a bad example by forcing a class-based API on its users! With the Hooks proposal, the React team has come to their senses and embraced a radically simpler functional approach. With that, the last reason for many developers to have to use classes at all has evaporated, and good riddance. I'll reiterate: everything you think you need classes for can be done better without them.

The future of JS and TS is classless, praise be!

@lucasbasquerotto

This comment has been minimized.

Copy link

lucasbasquerotto commented Dec 15, 2018

@cbutterfield I noticed that too, and if issue #9264 was closed to be discussed in this issue, I think the title should change to Suggestion: Final keyword for classes and methods, otherwise people looking for final methods (exclusively) might not add a reaction in the first post.

@bbottema

This comment has been minimized.

Copy link

bbottema commented Jan 4, 2019

Quoting @mhegazy:
Java and/or C# uses the final class to optimize your class at runtime, knowing that it is not going to be specialized. this i would argue is the main value for final support. In TypeScript there is nothing we can offer to make your code run any better than it did without final.

I fundamentally disagree. I've developed in Java for many years in many teams and we have never used final classes or methods for runtime optimizations; in practice it is primarily a OOP design idiom. Sometimes I just don't want my class or method to be extended/overridden by external subclasses be it to protect and guarantee a function, or to limit a library's API noise to the essentials, or to protect API users from misunderstanding a complex API.

Yes those issues can be solved by adding comments or interfaces, but final is an elegant solution to reducing visible API to the necessary functions when all you want is a simple class with a clean extendable API.

@Mcfloy

This comment has been minimized.

Copy link

Mcfloy commented Jan 21, 2019

Even though this issue is nearly 3 years old, we really should have a final keyword as there's already an abstract keyword.

Keep in mind that we do not want to force people to use it and it's a feature as useful as the abstract keyword. But here's another usecase that will strongly show the benefit of a final keyword:

abstract class A {
  protected abstract myVar: string;

  protected abstract myFunction(): void;
}

class B extends A {
  protected readonly myVar: string = "toto";

  constructor() {
    super();
    this.myFunction();
  }

  protected myFunction() {
    console.log(this.myVar);
  }
}

class C extends B {
  constructor() {
    super();
  }

  protected myFunction() {
    console.log("tata");
  };

  public callFunction = () => {
    this.myFunction();
  }
}

const myB = new B(); // toto

const myC = new C(); // toto
myC.callFunction(); // tata

Result after compilation :

toto
toto
tata

So in this code, We have an abstract class A that have abstract methods and properties, we want the inherited class to define them, but we would like to not let any other classes to override those implementations.
Best we could do would be to keep the protected keyword, but as you see, we can still redefine the attributes.

So what if we go further in the Typescript compilation process and use the readonly to protect our attributes (and pretend our methods are properties) ?

class B extends A {
  [...]

  protected readonly myFunction = () => {
    console.log(this.myVar);
  }
}

class C extends B {
  protected myVar: string = "I am not protected that much";
  [...]

  protected myFunction = () => { // using the readonly keyword doesn't prevent any modification
    console.log("tata"); // If you launch this code, tata will still be logged in the console.
  };
}

(Note that the code is compiled with tsc, no errors thrown when compiling or executing it)
So now we have two problems :

  • We don't protect our B attributes, so we need a way to prevent any unexpected inheritance.
  • Now we know that we can override any readonly properties by extending its class, and YES, Typescript knows that it's the parent property we're changing as using private instead of protected in C class will throw an error as it's not the same access type/visibility.

At the moment, we could use decorators (sealed like the official Typescript documentation shows, or a final decorator ) but remember that it is only useful in runtime, and that we must prevent this in the compilation process instead.

I'll look if there's an issue about the "security breach" (if we can call that) when we want to override a protected readonly value and we actually can and we shouldn't. Otherwise I'll open an issue to debate on the verification of the readonly keyword among private, protected keyword with inheritance stuff.

tl ; dr : The final keyword would solve two problems (though the readonly verification would be a good start to prevent any unauthorized rewrite)

@GuilhermeLessa

This comment has been minimized.

Copy link

GuilhermeLessa commented Jan 23, 2019

Three years later... it's so ridiculous.

@PavelKastornyy

This comment has been minimized.

Copy link

PavelKastornyy commented Jan 28, 2019

In order to prove the need for this feature, I suggest to take a look what was done in other languages:

Java:
final class SomeClass { ... }
PHP:
final class SomeClass { ... }
C#:
sealed class SomeClass {...}
C++:
class SomeClass final { ... }
Delphi:
type SomeClass = class sealed() ... end;

So, we see that in all the most popular OOP languages this feature exists because it comes from the inheritance logic of OOP.

@Mcfloy

This comment has been minimized.

Copy link

Mcfloy commented Jan 28, 2019

Also I have to quote the Dev lead of TS saying

if we have final classes, then we'll "need" final methods or properties as well.

I'm not sure if it's ironic, but in JS the readonly keyword is used for properties (and methods if you cheat), so that's a pretty dumb point.

And maybe not letting one guy closing the issue when he have +40 downvotes meaning the community disagree strongly with him would be a good advice to avoid further dumb situations.

If you're not interested in reinforcing the main feature of Typescript, please say it loud so that tech news can relay that on Twitter, because otherwise, it's a just an hidden bad buzz. You're just ignoring your community, AND you have no process in making the community validating what we need or not.

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Jan 28, 2019

maybe not letting one guy closing the issue when he have +40 downvotes meaning the community disagree strongly with him would be a good advice to avoid further dumb situations

I'm not "one guy". When I make decisions here, it is the reflection of the entire TypeScript team's decision-making process. The design team has looked at this issue multiple times, and each time come to the same conclusion. You are welcome to disagree with that outcome, but the process is not up for debate.

you have no process in making the community validating what we need or not.

This is the process, right here: You calling my summarization of our reasoning "dumb" and me (and other people on the team) reading it. We're listening to the feedback.

@PavelKastornyy

This comment has been minimized.

Copy link

PavelKastornyy commented Jan 28, 2019

@RyanCavanaugh We are very thankful to you and TypeScript team for your work! What we say is : "Look, feature that blocks class extending is obvious and implemented in the most popular languages, because developers need it. So, if it is possible to do in so many languages why it is not possible to implement in TypeScript. We do want/need TypeScript to be as good as possible". And this is the feedback you are speaking about.

@asimonf

This comment has been minimized.

Copy link

asimonf commented Jan 28, 2019

I know pointing to other implemented features as reasons for considering this one is a smell and not really an argument, but I find it ironic that a type aliasing mechanism was considered and added (it may well be awesome, but one could certainly live without it) but something like this is rejected.

In the end, I can live without such a feature, but the same could be said for about half the features TS has. So, then, what would be an appropriate reason to consider implementing this?

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

RyanCavanaugh commented Jan 28, 2019

GitHub unhelpfully hides a lot of comments here, so I'm reposting various responses (plus some additions) we've given in the past. Adding some thoughts in below as well.


Some points were considered when we reviewed this last time

  • CLASSES ARE A JAVASCRIPT FEATURE, NOT A TYPESCRIPT FEATURE. If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.
  • If it's legal to invoke new on something, that has a one-to-one runtime correspondence with inheriting from it. The notion of something that can be new'd but not super'd just doesn't exist in JavaScript
  • We are trying our best to avoid turning TypeScript into an OOP keyword soup. There are many better composition mechanics than inheritance in JavaScript and we don't need to have every single keyword that C# and Java have.
  • You can trivially write a runtime check to detect inheritance from a "should-be-final" class, which you really should do anyway if you're "worried" about someone accidently inheriting from your class since not all your consumers might be using TypeScript.
  • You can write a lint rule to enforce a stylistic convention that certain classes aren't inherited from
  • The scenario that someone would "accidently" inherit from your should-be-sealed class is sort of an odd one. I'd also argue that someone's individual assessment of whether or not it's "possible" to inherit from a given class is probably rather suspect.
  • There are basically three reasons to seal a class: Security, performance, and intent. Two of those don't make sense in TypeScript, so we have to consider the complexity vs gain of something that's only doing 1/3rd the job it does in other runtimes.

haven't heard a convincing argument for why TS doesn't need it

Just a reminder that this is not how it works. All features start at -100. From that post

In some of those questions, the questions asks why we either “took out” or “left out” a specific feature. That wording implies that we started with an existing language (C++ and Java are the popular choices here), and then started removing features until we got to a point where we liked. And, though it may be hard for some to believe, that's not how the language got designed.

We have a finite complexity budget. Everything we do makes every next thing we do 0.1% slower. When you request a feature, you're requesting that every other feature become a little bit harder, a little bit slower, a little bit less possible. Nothing gets in the door here because Java has it or because C# has it or because it'd only be a few lines of code or you can add a commandline switch. Features need to pay for themselves and for their entire burden on the future.


What would change our mind here? People showing up with examples of code that didn't work the way it should have because the final keyword was missing.

The scenario here that the keyword attempts to address is one where someone mistakenly inherits from a class. How do you mistakenly inherit from a class? It's not an easy mistake to make. JavaScript classes are inherently fragile enough that any subclass must understand its base class's behavioral requirements, which does mean writing some form of documentation, and the first line of that documentation can simply be "Do not subclass this class". Or there can be a runtime check. Or the class constructor can be hidden behind a factory method. Or any other number of options.

In other words: the only correct process for inheriting from a class in JavaScript always starts with reading that class's documentation. There are too many constraints the base class might have to simply derive and start coding. If, at the very first step, you should already know not to try, what value is the static enforcement providing at step three?

Why must TypeScript have this keyword when the situation is already one in which you should have had encountered at least two separate roadblocks telling you not to be there to begin with?

I've been programming for approximately forever years now and I have not once tried to subclass something only to find out it was sealed and I shouldn't have tried. It's not because I'm a super genius! It's just an extremely uncommon mistake to make, and in JavaScript these situations are already ones where you need to be asking questions of the base class in the first place. So what problem is being solved?

If you're not interested in reinforcing the main feature of Typescript, please say it loud so that tech news can relay that on Twitter

We're very explicit about how we design the language; non-goal number one speaks exactly to this suggestion.


My personal reaction after reading the many comments here is that there's a strong biasing effect due to constructs that exist in other languages. If you just approach this from a blank slate perspective, there are dozens of behavioral you could imagine, of which TypeScript has only a small subset.

You could easily imagine some keyword that was applied to a method and enforced that derived methods called it via super. If C# and Java had this keyword, people would absolutely apply that keyword in places where it made sense. In fact, it'd arguably be much more commonly enforced than final, because it's impossible to apply a runtime check for, and is a much more subtle aspect of the base-derived contract than "derived classes may not exist". It'd be useful in a variety of ways that final wouldn't be. I would much rather have that keyword than this one (though I think neither meets the complexity vs value bar).

So, then, what would be an appropriate reason to consider implementing this?

When we look at feedback, strong considerations look like this:

  • I can't adequately express the behavior of this JavaScript library
  • My code compiled, but really shouldn't have because it had this (subtle) error
  • The intent of this code is not adequately communicated by the type system

final hits these, but so would a modifier that says "This function can't be called twice in a row" or "This class's construction doesn't really finish until the next async tick". Not everything imaginable should exist.

We've never seen feedback like "I spent hours debugging because I was trying to inherit from a class that wasn't actually subclassable" - someone saying that would rightly trigger a 'wat' because it's not really imaginable. Even if the modifier existed, it wouldn't really help the situation, because libraries don't document if classes are intended to be final - e.g. is fs.FSwatcher final ? It seems like even the node authors don't know. So final is sufficient if the authors know that it's final, but that's going to be documented regardless, and a lack of final doesn't really tell you anything because it's often simply not known either way.

@Mcfloy

This comment has been minimized.

Copy link

Mcfloy commented Jan 28, 2019

I read the entire block and understand the statement of the team on the choice of features, I'll just go back on certain points from my comments

I'm not "one guy". When I make decisions here, it is the reflection of the entire TypeScript team's decision-making process.

Sorry I was refering to mhegazy and the pretty massive feedback he got from the community that uses Typescript.

If you really feel like classes are completely useless without final, that is something to take up with the TC39 committee or bring to es-discuss. If no one is willing or able to convince TC39 that final is a must-have feature, then we can consider adding it to TypeScript.

I do not think final is the first step as there's already a proposal for the private keyword https://github.com/tc39/proposal-private-methods

I'm skeptical about the You should a comment to say *dont do this*, it's like saying on a form Hey don't write down specific characters, you coded for nearly forever years so you should know the n°1 rule of a dev: Never trust the user (and the dev that will use your code)

I'm not saying we must absolutely stop everything and put a billion dollars to implement the final keyword, because the usecases are too low to be that efficient, also consider that I gave few exemples where the limit are both from TS and JS and that if people choose TS, it's to prevent the errors in compile-time, not in run-time. If we want things to blow up at runtime, we can use JS and stop caring about TS, but that's not the point, because there's an ultimate usecase that shows how I use the final keyword : I want to lock a method, I don't want anyone to override it.

And as Javascript is limited by that, the community thought Typescript could go beyond that limit, that's why this issue has been on for 3 years, that's why people are still wondering why this feature is not here, and that's why we want the compiler to do the manual checking of a class and/or method.

You didn't wait JS to have private/public methods to implement them, though there are actually proposals to include them with the # keyword (less verbose than public/private), I know you wanted to create a perfect language because perfection is not when you can't add anything anymore, it's when you can't remove anything anymore.

If you can find a solution to prevent a method to be overwritten in the compile process (and not in runtime-process as the point wouldn't be valid), be my guest.

I tried to show the weakness of the situation (on methods/properties and not class), because any TS dev can rewrite any libraries they want, breaking anything they want, maybe that's the beauty of the thing, I guess.

Thanks for the reply by the way, no hate or bad behavior intended against the dev team or you.

@thorek

This comment has been minimized.

Copy link

thorek commented Jan 28, 2019

@bbottema

This comment has been minimized.

Copy link

bbottema commented Jan 28, 2019

Yeah, the discussion is so diffuse now. I would like to see these issues in a tracker where you can vote, like the one used for IntelliJ / webstorm issues. Get some actual numbers of how many people would like to see final for classes and/or methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment