Skip to content

Proposal for Abstract Classes and Methods #3578

Description

@aozgaa

Proposal for Keyword abstract

This is a proposal for a new keyword, abstract, to allow for incomplete/partially implemented classes.

Introduction

This proposal expands upon #6, #2946, #2947.

Consider the following situation. A user would like to create a base class that shouldn't be instantiated as a template for derived classes. S/he might write something like

class Animal {
    public age : number;
    public yearsLeft() { return 20 - this.age; }
    public makeSound() : string { return "???"; }
}

class Cow extends Animal {
    makeSound() { return "Moo"; }
}

class Cat extends Animal {
    makeSound() { return "Meow"; }
}

Today, the writer is forced to make a choice: either (i) Animal can be declared an interface, but then yearsLeft cannot have an implementation, or (ii) write the program as above, but then Animal can be instantiated and makeSound has a bogus implementation! Neither of these options is particularly attractive.

We propose abstract as a class declaration modifier to allow the programmer control over whether a class can be instantiated, and as a member function modifier to control whether said member function offers an implementation (and whether the enclosing class is abstract).

Details

The modifier abstract can prepend a class declaration or a member function declaration. Constructors cannot be declared abstract.

  • Objects whose type is typeof C where C is an abstract-declared class (hereafter abstract class) cannot be instantiated via calls to new.
  • A member function declared abstract does not have an implementation.
  • Abstract methods cannot be called within a class member function body via super.foo() where foo is an abstract method belonging to the parent class.
  • Abstract methods may be called within a class member function body via this.foo() where foo is an abstract method belonging to that class.
  • If a class has any abstract methods, then the class itself must be declared abstract.
  • Abstract methods are inherited.
  • Abstract methods can be overridden in the derived class, either by an abstract method (effectively a re-declaration) or a function declaration that offers an implementation.
  • A method cannot be simultaneously abstract and static.
  • A method cannot be simultaneously abstract and private.
  • If an abstract member declaration contains an explicit accessibility modifier, then the modifier must precede the abstract keyword.
  • Overloads on a member function must be either all abstract or all not abstract.
  • Overloads of abstract member functions must be declared adjacent to eachother.
  • Objects whose type is the type of an abstract class can only be assigned to other objects whose type is the type of an abstract class. For example,

The following code snippet summarizes valid and invalid usages of the abstract keyword

class A {
    // ...
}

abstract class B {
    foo(): number { return bar(); }
    abstract bar() : number;
}

new B; // error

import myB = B;
new myB; // error

var BB: typeof B = B;
var AA: typeof A = BB; // error, AA is not of abstract type.
new AA;

function constructB(Factory : typeof B) {
    new Factory; // error -- Factory is of type typeof C
}

var BB = B;
new BB; // error -- BB is of type typeof C

var x : any = C;
new x; // okay -- undefined behavior at runtime


class C extends B { } // error -- not declared abstract

abstract class D extends B { } // okay

class E extends B { // okay -- implements abstract method
    bar() { return 1; }

abstract class F extends B {
    abstract foo() : number;
    bar() { return 2; }
}

abstract class F {
    abstract qux(x : number) : string;
    abstract qux() : number;
    y : number;
    abstract quz(x : number, y : string) : boolean; // error -- declarations must be adjacent

    abstract nom() boolean;
    nom(x : number) boolean: // error -- use of modifier abstract must match on all overloads.
}

class G { // error -- not declared abstract
    abstract baz() : number;
}

Pros

  • Allows the programmer to write partial implementations of a class.
  • Provides a mechanism for the type system to warn about classes that shouldn't be instantiated.
  • abstract annotations clarify the role of base classes in various patterns.

Cons

  • Increases the complexity of the language.

To Be Discussed

  • Should abstract be allowed within class expressions?

Proposed Changes to Grammar

A.6 Classes

  ClassDeclaration:
   abstractopt class Identifier TypeParametersopt ClassHeritage { ClassBody }

  MemberFunctionDeclaration:
   MemberFunctionOverloadsopt MemberFunctionImplementation
   AbstractMemberFunctionOverloads

  AbstractMemberFunctionOverloads:
   AbstractMemberFunctionOverload
   AbstractMemberFunctionOverloads AbstractMemberFunctionOverload

  AbstractMemberFunctionOverload:
   AccessibilityModifieropt abstract PropertyName CallSignature ;

A.10 Ambients

  AmbientClassDeclaration:
   abstractopt class Identifier TypeParametersopt ClassHeritage { AmbientClassBody }

  AbstractOrStatic:
   abstract
   static

  AmbientPropertyMemberDeclaration:
   AccessibilityModifieropt AbstractOrStaticopt PropertyName CallSignature ;

EDIT: updated to clarify the assignability of objects of "abstract type".

Activity

  1. CKGrafico commented on Jun 20, 2015

    @CKGrafico

    Thanks for that!

  2. mhegazy commented on Jun 20, 2015

    @mhegazy
    Contributor

    Moreover, the assignability of abstract types is restricted to instances that are abstract

    I do not understand what you mean by this.

  3. jbondc commented on Jun 22, 2015

    @jbondc
    Contributor
    • Increases the complexity of the language.

    Pretty much how I feel about it. I'd rather see a more lean functional-like TypeScript then adding astronaut like OO patterns like abstract or decorators.

    This overlaps tons with mixins or traits too (combining things from other things). The benefit of traits or mixins is you can design 'abstract patterns' that work with modules too. This proposal is limited to a 'class' which lets face it is really a function under the hood.

  4. aozgaa commented on Jun 22, 2015

    @aozgaa
    ContributorAuthor

    Mohamed Hegazy (@mhegazy) This definitely needs re-wording.

    We want to prevent the constructor of an abstract class from being invoked. So we wanted to restrict the assignability of the constructor function. Consider the following example:

    class A {}
    abstract class B extends A {}
    
    var BB: typeof B = B;
    var AA: typeof A = BB;
    new AA;

    We don't want the second assignment to be legal. So we restrict the assignabilty of objects whose type is the type of an abstract class to other objects whose type is the type of an abstract class.

    I haven't yet implemented this as part of the attached PR.

    I've updated the spec to clarify this.

  5. zpdDG4gta8XKpMCd commented on Jun 25, 2015

    @zpdDG4gta8XKpMCd

    Jon (@jbondc) so true, inheritance (for the sake of code "reuse" or whatever else) is a hands down rotten idea, composition does it all with a little bit more effort, screw this s*t

  6. Gaelan commented on Jun 25, 2015

    @Gaelan

    👍

  7. jbondc commented on Jun 25, 2015

    @jbondc
    Contributor

    💩 I'd love to hear how this aligns with the design goals:

    5. Produce a language that is composable and easy to reason about.
    6. Align with current and future ECMAScript proposals.
    
    Non-goals
    1. Exactly mimic the design of existing languages. Instead, use the behavior of JavaScript 
    and the intentions of program authors as a guide for what makes the most sense in the language.
    
  8. kitsonk commented on Jun 25, 2015

    @kitsonk
    Contributor

    I have long argued of the merits of embracing the protypical nature of ECMAScript instead of projecting a traditional OO paradigm. On the other hand, that hasn't stopped the wider community rejecting that and ECMAScript has taken a large bend towards traditional OO with ES6, though there seems much debate on this, as several of our ECMAScript luminaries seems to be conflicted on every aspect of this.

    Jon (@jbondc) I think it is a fair challenge to question this proposal in line with design goals 5 and 6. It is very likely that some sort of abstract/traits/composition type features will end up in ES, though there seems to be no common consensus. Both abstract and traits were proposed during the class discussions in ES6/2015, but all got dropped because no one could agree. TypeScript already has interfaces which provide a sufficient level of abstraction ahead of any implementation of something along this line of ES.

    The traits discussion (#311) was closed by Ryan Cavanaugh (@RyanCavanaugh) because of this conflicting with goal 6. I quote:

    There's a lot of overlap here with mix-ins; it would be bad (or at least treacherous) to do both. With mix-ins being a potential part of ES7/ES8+, we want to take the runtime side of this very conservatively and get TC39 to agree on a proposal in this area first.

    I honestly don't see what the difference is here.

    Jon (@jbondc) so true, inheritance (for the sake of code "reuse" or whatever else) is a hands down rotten idea, composition does it all with a little bit more effort, screw this s*t

    While I am personally a fan of compostability, a strong emotive response without well thought arguments is unlikely to further your thoughts, especially when it appears you want to throw your arms up in the air and walk away. I don't think that is your intent, but that is the way it comes across.

  9. zpdDG4gta8XKpMCd commented on Jun 25, 2015

    @zpdDG4gta8XKpMCd

    Here is the statement: inheritance is a dangerous tool that causes more problems than solves

    • it takes knowledge, practice and discipline to use it right (which junior/mid developers generally lack)
    • it takes experience to grasp the understanding where it may NOT be used
    • yet, given all of above, even experienced developers with well designed systems fall short when requirements change and it comes to redesign and it turns out that existing inheritance hierarchies are too rigid to change

    All this contributes to frustration, missed deadlines, and myths that coding is hard. In my experience nothing is hard if designed properly. Well, inheritance encourages bad design by allowing developers to violate:

    • (open closed principle)[https://en.wikipedia.org/wiki/Open/closed_principle]
    • (Liskov substitution principle)[https://en.wikipedia.org/wiki/Liskov_substitution_principle]

    Besides that

    • having to choose one single super-class among a few possible others inevitably favoring one predecessor over all equally important others, resulting to skewed hierarchies
    • in a sub-class, having to comply to the interface of the super-class even though it goes against the needs
    • leaving a class unclosed and allowing uncontrolled sub-classing that wasn't intended

    Besides that, there is quite a number of successful examples of languages without inheritance at all:

    • (COM)[https://www.microsoft.com/com/default.mspx] where code reuse is achieved with interface inheritance and composition only
    • functional lanaguges where code reuse is achieved by various means (parametrizing with functions, typeclasses, class modules) without having to alter the existing code
  10. DanielRosenwasser commented on Jun 25, 2015

    @DanielRosenwasser
    Member

    Alright, while we'd love to have a constructive conversation on the merits of different approaches in software design, I believe that classes are a useful abstraction and the abstract modifier would bring utility. For those who prefer using this paradigm, it would surely be helpful.

    Unless there are specific "gotchas" introduced in our language from abstract that are not found in other languages, I disagree that users would find this extremely complex. If one understands classes and one understands interfaces, then abstract classes do not require a massive cognitive leap.

  11. jbondc commented on Jun 25, 2015

    @jbondc
    Contributor

    Objective thinking:
    http://ocw.kfupm.edu.sa/ocw_courses/phase3/ICS201-Offering-052/Readings/p123-armstrong.pdf

    What bothers me the most is the increasing and consistent sidestepping of the 'TS design rules' in favor of:

    1. Whatever aligns with the Microsoft Way does not require any justification
  12. jbondc commented on Jun 25, 2015

    @jbondc
    Contributor

    Kitson Kelly (@kitsonk) I must have missed it, when was abstract ever proposed?
    http://wiki.ecmascript.org/doku.php?id=strawman:oldes6

  13. 26 remaining items

  14. danquirk commented on Jul 2, 2015

    @danquirk
    Member

    Aleksey-Bykov C# operates under a completely different set of constraints than TypeScript does. We could love pure functional programming more than anyone but it doesn't change the rules we actually need to operate with as we design for the future (ie conforming to future ECMAScript standards). If you want an arbitrary set of language features/semantics that compile to JS there are a lot of options, but TypeScript isn't one of them. There are also web centric languages that have opted to add desirable expression level features divorced from ECMAScript standards. The value/success of these two divergent strategies is left as an exercise for the reader ;)

    Suffice to say, we love designing new language features and want to keep adding stuff that makes everyone's lives easier, but it's simply not the case that we get to just pick our favorite features from Haskell, Smalltalk, etc and put them into TypeScript with no regard for future ECMAScript incompatibility they may/will cause.

  15. Yves57 commented on Sep 3, 2015

    @Yves57

    Great job!
    Just one question: it seems that properties cannot be abstract.
    The code public abstract get myProp(): string; is not accepted.
    Limitation or bug?

  16. mhegazy commented on Sep 3, 2015

    @mhegazy
    Contributor

    Yves Marx (@Yves57) this is by design. there is no way to currently to distinguish between a property accessor and a property declaration, so a derived class can define a property declaration and would meet abstract constraints, but probably this is not what you intended by making the accessor abstract in the base.

  17. Yves57 commented on Sep 4, 2015

    @Yves57

    Mohamed Hegazy (@mhegazy) To be more precise, my case is the following

    interface IMyInterface { myProp: string; }
    abstract class MyBase implements IMyInterface { abstract get myProp(): string; ......... }
    

    But I understand that this is a specific case!

  18. denvned commented on Sep 5, 2015

    @denvned
    Contributor

    Yves Marx (@Yves57), I've faced exactly same problem. Looks like this case isn't so rare...

    It would be better if abstract class was allowed to miss fields from it's interface (both value properties and methods), like in other OOP languages.
    So, this should be allowed:

    interface A { p; m(); }
    abstract class B implements A {}
    class C extends B { get p() {...}; set p(v) {...}; m() {...} }

    But currently it gives:

    Error:(2, 16) TS2420: Class 'B' incorrectly implements interface 'A'. Property 'p' is missing in type 'B'.

  19. denvned commented on Sep 5, 2015

    @denvned
    Contributor

    Also I think that abstract value properties should be allowed in abstract classes (same way value properties are now allowed in interfaces):

    abstract class A {
      abstract p;
    }
    class B extends A {
      get p() {...}
      set p(v) {...}
    }
  20. mhegazy commented on Sep 5, 2015

    @mhegazy
    Contributor

    Denis Nedelyaev (@denvned) can you file separate issues to track the two requests, 1. abstract properties, and 2. missing property declaration in abstract class implementing interfaces.

  21. electricessence commented on Nov 3, 2015

    @electricessence

    I'm trying to use this in WebStorm 11 with TS1.6... Not working?

  22. DanielRosenwasser commented on Nov 3, 2015

    @DanielRosenwasser
    Member

    electricessence WebStorm doesn't use our language service, so unless this is an issue you're seeing at compilations (i.e. not while editing) you should probably check in with them.

    Though, it looks like they should support it: http://blog.jetbrains.com/webstorm/2015/09/webstorm-11-eap-142-4723/

  23. AJamesPhillips commented on Dec 8, 2015

    @AJamesPhillips

    Bryan Rayner (@bryanerayner) +1 for:

    Aleksey-Bykov, I'd actually love to have mixins as a feature, but can't comprehend doing it without structured type support.

    Have you started submitting a proposal for mixins with typescript? If you haven't, are there good references to prior work where someone might begin working on fleshing that out?

  24. AJamesPhillips commented on Dec 8, 2015

    @AJamesPhillips

    Aleksey-Bykov agreed, I shouldn't have said mixins as practically they share major downsides with multiple inheritance. For the moment I'm:

    • making sure my inheritance trees are as shallow as possible
    • using the is-a guideline
    • utility functions
    • And when all that fails because you have multiple base classes something "must" inherit from (and which utility functions can't deal with elegantly) then public and or private attributes set to helper functions and or class instances. If the helper functions and or class instances also need a reference to the instance then I set them in the constructor and pass this to them then.

    Would be very interested to see composition / stamps / aspect / traits, in TypeScript. And thanks for the link to Dan Abramov's article.

  25. electricessence commented on Dec 12, 2015

    @electricessence

    It works in 11.

    From: Daniel Rosenwasser [mailto:notifications@github.com]
    Sent: Tuesday, November 03, 2015 3:44 PM
    To: Microsoft/TypeScript
    Cc: electricessence
    Subject: Re: [TypeScript] Proposal for Abstract Classes and Methods (#3578)

    electricessence https://github.com/electricessence WebStorm doesn't use our language service, so unless this is an issue you're seeing at compilations (i.e. not while editing) you should probably check in with them.

    Though, it looks like they should support it: http://blog.jetbrains.com/webstorm/2015/09/webstorm-11-eap-142-4723/

    —
    Reply to this email directly or view it on GitHub #3578 (comment) .Image removed by sender.

  26. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CommittedThe team has roadmapped this issueFixedA PR has been merged for this issueSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions