Skip to content
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

Feature Request / Proposal: Traits #311

Closed
yasselavila opened this issue Jul 30, 2014 · 31 comments
Closed

Feature Request / Proposal: Traits #311

yasselavila opened this issue Jul 30, 2014 · 31 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@yasselavila
Copy link

ORIGINALLY PROPOSED IN http://typescript.codeplex.com/workitem/838

Traits, as "compile-time" partial classes, would perfectly fit with TS ideology and resolve problems of multiple inheritance/mixins.

Traits in Scala: http://en.wikibooks.org/wiki/Scala/Traits
Traits in PHP: http://php.net/trait

I propose minimal traits similar to PHP implementation.

trait FooTrait {
   // same as class definition, but no constructor allowed, e.g.:
   public foo() {
       return "foo";
   }
   private bar() {
       return "bar";
   }
}

class Foo {
    import FooTrait; //not insisting on exact keyword and syntax, just smth to start with

    // ...
}

class Bar {
    // rename methods:
    import FooTrait(foo => tfoo, bar => tbar);

    // and to include another trait, there is another import line:
    // import BarTrait;

    // ...
}

The code above could be compiled to JS below (can be optimized, just showing the main idea):

var FooTrait = (function () {
    function FooTrait() {
        throw "Cannot instantiate trait FooTrait";
    }
    FooTrait.prototype.foo = function () {
        return "foo";
    };
    FooTrait.prototype.bar = function () {
        return "bar";
    };
    return FooTrait;
})();

var Foo = (function (_super, FooTrait) {
    function Foo() { }
    Foo.prototype.foo = FooTrait.prototype.foo;
    Foo.prototype.bar = FooTrait.prototype.bar;
    return Foo;
})(undefined, FooTrait);

var Bar = (function (_super, FooTrait) {
    function Bar() { }
    Bar.prototype.tfoo = FooTrait.prototype.foo;
    Bar.prototype.tbar = FooTrait.prototype.bar;
    return Bar;
})(undefined, FooTrait);

Unresolved name conflicts should raise a compile time error.

I think it would be a great advance for the language. But the proposal has more than one year made (in codeplex) ​​and has not yet been implemented.

@ComFreek
Copy link

I think Mixins are relatively similar to traits, however, there are a few disadvantages (at least as it's described in the handbook):

  1. You have to redeclare class members and methods in the implementing class to satisfy the compiler.
  2. You have to "apply" the mixins (i.e. copy the class members and methods from the mixin class) using a custom function.

Are the points 1 and 2 by design or are these feature yet to be implemented?

@yasselavila
Copy link
Author

Mixins is an ugly way that feels like we are using ecmascript 3. Traits would allow us to use a more object oriented clean syntax.

@kamranayub
Copy link

This would certainly be awesome. I don't care how it's done, but this would be great--I use TS mainly in games and engines, and the ability to "mixin" methods from a trait would be great for actors/objects in games (for example, an object importing an EventDispatcher but inheriting from a base class).

class Actor {
  x: number;
  y: number;
  // ... etc
}

class Player extends Actor {
  import Traits.Eventing.EventDispatcher;

  update(engine) {
    // using my event dispatcher trait
    this.publish(new Event(...));
  }
}

I don't know how this will work with TS to allow using traits like interfaces (maybe simply traits can implement interfaces).

class SomeClass {
  public listen(emitter: EventDispatcher) {
    emitter.on("eventName", this.handleSomeEvent);
  }
  handleSomeEvent(args) {
  ...
  }
}
new SomeClass().listen(player);

@jbman
Copy link

jbman commented Oct 4, 2014

Traits as part of Typescript and its type system would be really helpful to use the DCI programming paradigm with (http://en.wikipedia.org/wiki/Data,_context_and_interaction).

I would prefer a trait solution, where the trait is an interface with a default implementation. Such an interface alone would not generate any JS code on compilation.
Like in Scala, such a trait-interface can now be used in two ways:

  • The interface can be implemented by a class: The class will get the methods "mixed in" (like in @yasselavila code in the first post) .
    The class may need to implement methods which don't have a default implementation
  • The interface can be added to an object when constructing the instance.

Here is an example:

// Trait-Interface with default implementation
interface Customer {
    isGoldMember: false;
    public discountFactor(): number {
       return (this.isGoldMember ? 0.1 : 0); 
    }
}

// Trait-Interface is implemented
class MyGoldCustomer implements Customer {
       public isGoldMember() { return true; }
}

// Trait-Interface is used on instantiation
class Person {
      constructor(private name: string) {};
      public getName():String { return name; };
      // ...    
}

var p = new Person('John') with Customer; 
// p is of type Person AND Customer. The new object gets the methods "mixed in".
// The type of p is the same type as class X, if X is defined as "class X extends Person implements Customer"

The "with" syntax is borrowed from Scala. Of course some other syntax could be used for mixing trait interfaces into a new instance, e.g.:
new Person('John') & Customer;
This syntax would fit to union typers (#805). With traits we have another sort of union type, but the instance is implementing both types, not one of them.

I'm curious if this suggestion is compatible with the current Typescript type system. Any thoughts?

@anatoliyarkhipov
Copy link

Also, without multiple inheritance or traits, not possible to use TypeScript with Dojo Toolkit. The whole library is built on mixins system. Of course, we can use default "dojo/_base/declare", but this way negates the benefits from classes and interfaces in TypeScript.

@csnover
Copy link
Contributor

csnover commented Feb 26, 2015

TypeScript team, are you guys open to receiving possible implementations of this feature?

@danquirk
Copy link
Member

We'd be open to implementations in the future but it's important that we first nail down a proposal we can all agree on. It'd be getting ahead of ourselves to start reviewing an implementation before we nail down what is and isn't the intended behavior here.

@csnover
Copy link
Contributor

csnover commented Feb 27, 2015

No problem. SitePen is currently working on defining some package specifications for Dojo and this is one of the areas that we are looking to improve, so once we get a bit further along and actually begin the work of defining technical proposals those will be brought here. A prototype implementation would be useful to inform that decision-making and may form a suitable foundation for a final implementation, but these are early days, we have no code yet. I just didn’t want to have anyone on this side start going down the road and then find out that there is no chance to accept this, or that after talking to @mhegazy about C3 linearization that you guys wanted to move in that direction instead.

@csnover
Copy link
Contributor

csnover commented Feb 28, 2015

@jbondc Great! Looking over what you are proposing so far I would probably start by just focusing on the compiler side of things and not even thinking about any runtime enhancements in order to keep the initial feature very clear and focused (like the OP).

I understand not wanting to introduce new reserved words that weren’t future reserved words in the EcmaScript spec but import class is very unfortunate. The strawman introduced a “trait” adjective for class (trait class), which is better.

I/others will have additional feedback over the coming months on this.

@NoelAbrahams
Copy link

@jbondc, there is a similar proposal on the old codeplex site. What do you think about it?

@csnover
Copy link
Contributor

csnover commented Mar 13, 2015

Just a brief update for everyone watching: I should have a complete(ish) proposal to share in the next couple of days at the latest. Sorry for the delay.

@csnover
Copy link
Contributor

csnover commented Mar 16, 2015

Our proposal is now available at https://docs.google.com/a/sitepen.com/document/d/112Xw-t8eh-eFIdKhn7LeDbGhO86XwlKv-Fwc6j9U-Oc/edit for discussion. (Please let me know if you prefer this to be in another format/location.) This proposal focuses purely on enhancing the compiler and doesn’t include any runtime enhancements.

Looking forward to your feedback and working with the rest of the team and community on refining these proposals into something that will be accepted into TypeScript.

@yasselavila
Copy link
Author

@csnover +1... but ... what about use only 'trait' instead of 'traitclass'?

@RichiCoder1
Copy link

@csnover agreed with @csnover. trait class seems very...peculiar to me. Especially since it, in the end, shares very little in common with class.

@csnover
Copy link
Contributor

csnover commented Mar 16, 2015

The use of an adjective was taken from the ES strawman, I have no particular affinity for that grammar. This really is a first draft to get feedback so if people think the adjective is wrong then I’ll update the proposal until mostly everyone is happy :)

(I personally prefer multiple inheritance with C3 over traits since it covers more common use cases and is usually more intuitive for people that already know how single-inheritance works.)

@csnover
Copy link
Contributor

csnover commented Mar 31, 2015

TypeScript team, do you have any feedback or can you let me know when you might have time to review this proposal? Is there information that is unclear that needs to be clarified before additional feedback can be solicited?

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus and removed Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Mar 31, 2015
@RyanCavanaugh
Copy link
Member

I think the proposal is very clear. We're really busy wrapping up ES6 stuff at the moment so I can't give any hint of a timeline at this point.

@qc00
Copy link

qc00 commented Apr 7, 2015

This might qualify as a separated feature, but I am wondering can a runtime-only version of traits be implemented so that native objects can be augmented in a more OO fashion.

For example, say I define a "runtime trait" like

trait ChildList extends Element {
    get childList(): Element[] { /* iterate the HTMLCollection ... */ }
}

and then use it like (someElement with ChildList).childList....

Emit for the trait could be the same as the current compile-time version (i.e. as prototype properties in a Function). For the call site, Function.apply can be used.

In the type system, such trait can be treated like an interface. (With the same instanceof problem that plagues the current mixins and the trait proposal above.)

There can even be some Scala-inspired magic like implicit conversion (probably module-based to maintain sanity).

@csnover
Copy link
Contributor

csnover commented Apr 10, 2015

Thanks everyone for the feedback and sorry for the delay.

It would be nice to have a generic mechanism for combining types (like a with or +) but you would need to have a way to resolve conflicts which is isn’t possible with just a simple operator.

@jbondc

Added the 'constructor' option back.

Agree on the constructors in traits, that was a mistake, can you put a comment on the proposal so I can remember to fix it there?

I didn't understand the part 'They also enable automatic cross-cutting of concerns simply by inheriting multiple mixins, which is not possible with traits (in a traits model, the methods would conflict and could not chain together with super calls).'

OK, no worries, I will try to clarify in the proposal. To give a little more background here, one reason to prefer C3 over traits is to avoid having to do a lot of (keyboard) typing especially when you are composing things together. It also lets the consumer of a library avoid understanding the right method ordering for every conflict method in order for it to work.

So, as a concrete example, dgrid, a responsive data grid widget, allows users to enhance its functionality by composing together classes adding in mixins/extensions. Users start with a base Grid, add keyboard navigation by mixing in Keyboard, and add column resizing by mixing in ColumnResizer, etc. Even though some of these mixins implement the same methods, everything almost always Just Works transparently and the user doesn’t have to think about sorting out how the super-calls should be ordered. So they can write:

class MyGrid extends Grid, Keyboard, ColumnResizer {}

and they are finished and everything just works.

In contrast using traits there would be numerous method conflicts, buildRendering, destroy, postCreate, renderHeader so it would end up needing to be more like this:

class MyGrid extends Grid {
  use Keyboard {
    destroy as destroyKeyboard,
    postCreate as pcKeyboard,
    // ... more here ...
  };
  use ColumnResizer {
    destroy as destroyColumnResizer,
    // ... more here ...
  };

  destroy() {
    super.destroy();
    this.destroyKeyboard();
    this.destroyColumnResizer();
  }

  postCreate() {
    return this.pcKeyboard(super.postCreate());
  }
}

And then if you ever change the way your grid is constructed you’d have to go through and find and fix all these methods explicitly too. Lots of work compared to letting C3 take care of it for you! (Replace the method renaming with symbols to the traits and it’s the same problem.)

https://gist.github.com/jbondc/b09d56dc4b8ed6c43af6

From what I see you are introducing a conflict since you have traits A and B used by class C and trait A also extends trait B, how would this be resolved? I don’t see any conflict resolution in this feedback gist. C3 automatically fixes this ambiguity by reordering dependencies of all inherited classes.

@csnover
Copy link
Contributor

csnover commented Apr 10, 2015

Also, sorry, somehow I neglected to link to wikipedia on C3 linearization for more background on how the algorithm works (it’s pretty straightforward if you can get past the pseudo-code, which makes it look more confusing than it is!), I added the link in the proposal.

@anatoliyarkhipov
Copy link

@jbondc

As long as the composing class implements all conflicting methods, there is no conflict reported by the compiler:

To be able to do this, programmer must know about all methods in all imported mixins and which of them are in conflict. Why he should if it is possible resolve this automatically like dojo/declare does (like in dstore example by @csnover)?

@RyanCavanaugh RyanCavanaugh added Declined The issue was declined as something which matches the TypeScript vision and removed In Discussion Not yet reached consensus labels Apr 27, 2015
@RyanCavanaugh
Copy link
Member

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.

Features specific to the type system to support these patterns are still on the table, but we'll track those in other issues.

@zakhenry
Copy link

Is there an update on how/if mixins will become a first class capability of typescript rather than a bit of a hack with separated registration as it currently stands?

@kitsonk
Copy link
Contributor

kitsonk commented Jan 22, 2016

Is there an update on how/if mixins will become a first class capability of typescript rather than a bit of a hack with separated registration as it currently stands?

I am sure I will be corrected if I am wrong, but the TypeScript team have made it clear that because this is a domain that TC39 has indicated they will address, the team feel that this is something it doesn't feel it can introduce as it would likely cause incompatibilities with ES in the future.

The implications are, those of us who are passionate about it should be championing it within TC39. The concepts of mixins/traits were part of the originally Harmony proposals, but didn't make it into the spec. Also, as far as I am aware, there isn't any active advocacy on this specific area in TC39 at the moment. Once there is a proposal sufficiently through the process in TC39, then I believe the TypeScript team would be more than glad to implement it.

@zakhenry
Copy link

@kitsonk thanks for the update, I guess we just have to hope for TC39, I'll see if I can find any discussion on the matter. I feel at this point that traits are the only major capability missing - I'm currently managing a site with ~50k sloc TS, and the mixin registrations are somewhat unwieldy.

@sanex3339
Copy link

+1 for this feature

@ackimwilliams
Copy link

What's the status on this feature?

@dylans
Copy link

dylans commented Feb 22, 2017

What's the status on this feature?

See https://blogs.msdn.microsoft.com/typescript/2017/02/02/announcing-typescript-2-2-rc/ ... basically they're not adding new language grammar, but by making some changes to the way Classes work, it's possible/easier to achieve now.

@alexeygolev
Copy link

One powerful pattern that is possible with traits is this (similar to what @jbman mentioned):

trait AccountService {
  debit: (account: Account, amount: number) => Account;
  credit: (account: Account, amount: number) => Account;
  transfer(from: Account, to: Account, amount: number): [Account, Account, number] {
    return [this.debit(from, amount), this.credit(to, amount), amount]  
  }
}

This is not something that TC39 can help us with as in this particular form it's only relevant to statically typed languages. It can be done with abstract classes but then we lose the benefit of the mixin pattern as we move the problem into the inheritance area. Trait composition also makes pattern such as "cake" pattern possible.

@samfrances
Copy link

+1

@michaelolof
Copy link

michaelolof commented Nov 4, 2017

You can have a look at a simple library i wrote at TypeScript Mix I think it fits nicely into how you would want to use traits/mixins

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests