Skip to content

Proposal : rest instance variables #6502

Closed

Description

@xmehaut

In order to implement in a simple manner mixins, it could be interesting to provide a kind of rest mechanism for instances variables (and methods).

Imagine the following syntax :

     class OtherClass {
           dummy1 : string;
           dummy2 : number;
           dummy() {
            }
     }
     class MyGenericClass <aClass> {
           ...: aClass;
     }
     class MyTerminalClass extends MyGenericClass<OtherClass> {
            aMethod() {
                  let toto = this.dummy1;
                  let titi = this.dummy2;
                  this.dummy();
            }
     }

This could be a nice way to define and use mixins...

We may also combine mixins like this :

      ... : OtherClass & OtherOtherClass ;

Activity

  1. mhegazy commented on Jan 16, 2016

    @mhegazy
    Contributor

    how is that diffrent from:

    class MyGenericClass <aClass> extends aClass {
    }
  2. xmehaut commented on Jan 16, 2016

    @xmehaut
    Author

    It is different because we expans in a class the content of other(s) classes, ie like mixins do, enabling in a certain manner multiple inheritance we don t have in modern languages , we had in first Smalktalk versions.

    Envoyé de mon iPhone

    Le 16 janv. 2016 à 01:06, Mohamed Hegazy notifications@github.com a écrit :

    how is that diffrent from:

    class MyGenericClass extends aClass {
    }
    —
    Reply to this email directly or view it on GitHub.

  3. xmehaut commented on Jan 16, 2016

    @xmehaut
    Author

    One example of use :
    Imagine you have two react ui components like container and paper.
    We could also have a third one which is containerpaper which fuses the features of the two previous ones. Instead of creating this third component, we could inject container capabilities into paper (or the contrary).

    Envoyé de mon iPhone

    Le 16 janv. 2016 à 01:06, Mohamed Hegazy notifications@github.com a écrit :

    how is that diffrent from:

    class MyGenericClass extends aClass {
    }
    —
    Reply to this email directly or view it on GitHub.

  4. jods4 commented on Jan 18, 2016

    @jods4

    I think the same effect could be achieved by having more freedom in the "extend from expression" feature. It is almost possible today with this syntax:

    class X extends Mixin<A,B>() {
    }

    And I would very much prefer that syntax over the ...: A; ...: B proposed.

    Now, it seems to me that this is impossible in TS 1.7 because when I try that:

    function Mixin<T,U>(t: new() => T, u: new() => U): new() => T & U { ... }

    the TS compiler complaints that "Base constructor return type 'T & U' is not a class or an interface.".
    This is a little sad and if the restriction might be lifted somehow you would have nice Mixins in the langage with a single helper function. Is there (or could there be) a way to say that T and U generics parameters are constrained to classes and interfaces, so T & U is OK for the compiler?

    You can make it work today if you accept creating one interface for each of your mixins combinations, like so:

    // Your global Mixin helper function
    function Mixin<Target, Src1, Src2>(a: new() => Src1, b: new() => Src2) : new() => Target { ... }
    
    // Sample mixins you want to use
    class Dog { 
      bark() {...} 
    }
    class Duck { 
      quack() {...}
    }
    
    // This is the extra interface that you need, it's empty: just a one-liner
    interface DoggyDuck extends Mixin1, Mixin2 { }
    
    // Now you can easily create derived classes 
    class Mutant extends Mixin<DoggyDuck, Dog, Duck>(Dog, Duck) { ... }
    
    // It works well at compile time and run time!
    var xavier = new Mutant();
    xavier.bark();
    xavier.quack();

    And I think with TS 1.8 F-Bounded Polymorphism we might get extra safety allowing a constraint that Target extends Src1 & Src2.

  5. xmehaut commented on Jan 19, 2016

    @xmehaut
    Author

    Thanks jod for this nice piece of code, nevertheless a little bit tricky :)

    Nevertheless, multi inheritance have some drawbacks we must face on like what about same methods defined in the different classes to be "merged", or same instance variables initialized differently? Except these cases, mixins are good means to avoid redefining some components when we need to have a tomato which is either a fruit and a vegetable :)

    Personnally, i like the syntactic sugar i proposed :
    ... : A&B&C

    It is quite concise and auto explained

    Envoyé de mon iPhone

    Le 19 janv. 2016 à 00:57, jods notifications@github.com a écrit :

    I think the same effect could be achieved by having more freedom in the "extend from expression" feature. It is almost possible today with this syntax:

    class X extends Mixin<A,B>() {
    }
    And I would very much prefer that syntax over the ...: A; ...: B proposed.

    Now, it seems to me that this is impossible in TS 1.7 because when I try that:

    function Mixin<T,U>(t: new() => T, u: new() => U): new() => T & U { ... }
    the TS compiler complaints that "Base constructor return type 'T & U' is not a class or an interface.".
    This is a little sad and if the restriction might be lifted somehow you would have nice Mixins in the langage with a single helper function. Is there (or could there be) a way to say that T and U generics parameters are constrained to classes and interfaces, so T & U is OK for the compiler?

    You can make it work today if you accept creating one interface for each of your mixins combinations, like so:

    // Your global Mixin helper function
    function Mixin<Target, Src1, Src2>(a: new() => Src1, b: new() => Src2) : new() => Target { ... }

    // Sample mixins you want to use
    class Dog {
    bark() {...}
    }
    class Duck {
    quack() {...}
    }

    // This is the extra interface that you need, it's empty: just a one-liner
    interface DoggyDuck extends Mixin1, Mixin2 { }

    // Now you can easily create derived classes
    class Mutant extends Mixin<DoggyDuck, Dog, Duck>(Dog, Duck) { ... }

    // It works well at compile time and run time!
    var xavier = new Mutant();
    xavier.bark();
    xavier.quack();
    And I think with TS 1.8 F-Bounded Polymorphism we might get extra safety allowing a constraint that Target extends Src1 & Src2.

    —
    Reply to this email directly or view it on GitHub.

  6. xmehaut commented on Jan 19, 2016

    @xmehaut
    Author

    Hi jods
    Is there a means to do the same by using decorators to inject the mixin classes into the current one?

    Envoyé de mon iPhone

    Le 19 janv. 2016 à 00:57, jods notifications@github.com a écrit :

    I think the same effect could be achieved by having more freedom in the "extend from expression" feature. It is almost possible today with this syntax:

    class X extends Mixin<A,B>() {
    }
    And I would very much prefer that syntax over the ...: A; ...: B proposed.

    Now, it seems to me that this is impossible in TS 1.7 because when I try that:

    function Mixin<T,U>(t: new() => T, u: new() => U): new() => T & U { ... }
    the TS compiler complaints that "Base constructor return type 'T & U' is not a class or an interface.".
    This is a little sad and if the restriction might be lifted somehow you would have nice Mixins in the langage with a single helper function. Is there (or could there be) a way to say that T and U generics parameters are constrained to classes and interfaces, so T & U is OK for the compiler?

    You can make it work today if you accept creating one interface for each of your mixins combinations, like so:

    // Your global Mixin helper function
    function Mixin<Target, Src1, Src2>(a: new() => Src1, b: new() => Src2) : new() => Target { ... }

    // Sample mixins you want to use
    class Dog {
    bark() {...}
    }
    class Duck {
    quack() {...}
    }

    // This is the extra interface that you need, it's empty: just a one-liner
    interface DoggyDuck extends Mixin1, Mixin2 { }

    // Now you can easily create derived classes
    class Mutant extends Mixin<DoggyDuck, Dog, Duck>(Dog, Duck) { ... }

    // It works well at compile time and run time!
    var xavier = new Mutant();
    xavier.bark();
    xavier.quack();
    And I think with TS 1.8 F-Bounded Polymorphism we might get extra safety allowing a constraint that Target extends Src1 & Src2.

    —
    Reply to this email directly or view it on GitHub.

  7. jods4 commented on Jan 19, 2016

    @jods4

    xmehaut
    I'm pretty sure you can inject your mixins at runtime using decorators, which is a nice syntax for that.
    But I don't think there is a way to augment the static, compile-time type with decorators, which is a huge drawback: all your mixins have to be handled as any types :(

  8. mhegazy commented on Jan 19, 2016

    @mhegazy
    Contributor

    It is different because we expans in a class the content of other(s) classes, ie like mixins do,

    so who is responsible for mixing in the new class at runtime? do you expect the generated code to do this for you? or you will be calling some extends method later on?
    if it is the erlieri do not see how generrics pattern can ever be used here, and you should probally look into #2919 instead.

  9. xmehaut commented on Jan 19, 2016

    @xmehaut
    Author

    The generics into the example are not related with the expansion. We could without by just writing ... : type1 & type 2

    Yes i would intend that the expansion would generate the js mixin code

    Envoyé de mon iPhone

    Le 19 janv. 2016 à 20:18, Mohamed Hegazy notifications@github.com a écrit :

    It is different because we expans in a class the content of other(s) classes, ie like mixins do,

    so who is responsible for mixing in the new class at runtime? do you expect the generated code to do this for you? or you will be calling some extends method later on?
    if it is the erlieri do not see how generrics pattern can ever be used here, and you should probally look into #2919 instead.

    —
    Reply to this email directly or view it on GitHub.

  10. mhegazy commented on Jan 19, 2016

    @mhegazy
    Contributor

    how is this different from #2919 or #311? is it just the syntax?

  11. xmehaut commented on Jan 19, 2016

    @xmehaut
    Author

    it is the same purpose , yes, and the syntax is a little bit different
    beacuse they refer more to rest desconstructing than to inheritance-like
    formalism... It is imprtant in the sense that we don't want to mimic
    multinheritance, but we want more a copy-like behavior... For instance, if
    we have in one of a mixin a method which has the same signature than the
    one of the target class, it won't be copied and not overwrite the one in
    place; the same for constructor or instance variables...

    2016-01-19 20:34 GMT+01:00 Mohamed Hegazy notifications@github.com:

    how is this different from #2919
    #2919 or #311
    #311? is it just the
    syntax?

    —
    Reply to this email directly or view it on GitHub
    #6502 (comment)
    .

  12. added
    SuggestionAn idea for TypeScript
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Jan 20, 2016
  13. Izhaki commented on Jan 20, 2016

    @Izhaki

    jods (@jods4)

    You can make it work today if you accept creating one interface for each of your mixins combinations, like so:

    Can you provide a working example on http://www.typescriptlang.org/Playground ?

  14. jods4 commented on Jan 20, 2016

    @jods4

    Izhaki Something along those lines. Note that I simplified the Mixin signature a bit.

    Playground

    function Mixin<T>(...mixins) : new() => T {
        class X {}
        Object.assign(X.prototype, ...mixins.map(m => m.prototype));
        return <any>X;
    }
    
    abstract class Dog {
        bark() { console.log('bark!'); }
    }
    
    abstract class Duck {
        quack() { console.log('quack!'); }
    }
    
    interface DoggyDuck extends Dog, Duck { }
    
    class Mutant extends Mixin<DoggyDuck>(Dog, Duck) {
    
    }
    
    let xavier = new Mutant();
    xavier.bark();
    xavier.quack();
  15. 1 remaining item

  16. xmehaut commented on Jan 20, 2016

    @xmehaut
    Author

    very nice!

    2016-01-20 16:25 GMT+01:00 Izhaki notifications@github.com:

    jods (@jods4) https://github.com/jods4 This is outright genius!

    I've taken the liberty to:

    • Synthesise your Mixin template with the one in the handbook, so:
      • It distinguishes between the base class and mixins
      • It doesn't require Object.assign
    • Extend the example to show a normal class hierarchy.

    I suspect Mixin() can be further improved, but this works:

    Playground

    function Mixin( baseClass: any, ...mixins ) : new() => T {
    mixins.forEach( mixin => {
    Object.getOwnPropertyNames( mixin.prototype ).forEach( name => {
    baseClass.prototype[ name ] = mixin.prototype[ name ];
    });
    });
    return baseClass;
    }

    // The mixin
    abstract class Painter {
    paint() { console.log( 'paint' )}
    }

    // Class hierarchy Animal <-- Mammal <-- Human

    class Animal {
    constructor() { console.log( 'Animal constructor' ); }
    eat() { console.log( 'Animal eats' ); }
    }

    interface AnimalPainter extends Animal, Painter {}
    class Mammal extends Mixin( Animal, Painter ) {
    constructor() {
    super();
    console.log( 'Mammal constructor' );
    }

    eat() {
        super.eat();
        console.log( 'Mammal eats' );
    }
    

    }

    class Human extends Mammal {
    constructor() {
    super();
    console.log( 'Human constructor' );
    }

    eat() {
        super.eat();
        console.log( 'Human eats' );
        this.paint();
    }
    

    }

    let iHuman = new Human();
    iHuman.eat();

    Mohamed Hegazy (@mhegazy) https://github.com/mhegazy, I reckon this is much better than
    the current mixin strategy in the handbook, as it removes the redeclaration
    issue. Your thoughts?

    —
    Reply to this email directly or view it on GitHub
    #6502 (comment)
    .

  17. jods4 commented on Jan 20, 2016

    @jods4

    Izhaki No problem with the changes, my code was a quick proof of concept ;)

    Just one point:
    baseClass.prototype[ name ] = mixin.prototype[ name ]

    Aren't you mutating your base classe ctor?
    I think you need to create a new intermediate base class (like I did with X).

  18. Izhaki commented on Jan 20, 2016

    @Izhaki

    jods (@jods4)

    Beat me to it, but it works. I initially had a condition for 'constructor', but it works without that condition. It doesn't seem to override the base class one (although the mixin does have a constructor own property).

    If you look at the code emitted, in extend, there's object.create(b) which means that the actual constructor should be the right one (the actual constructor is not the prototype property, but the 'private' javascript prototype.

  19. jods4 commented on Jan 20, 2016

    @jods4

    Izhaki I am not sure what you mean by "it works".
    Starting with your code I can do this:

    let bug = new Animal();
    bug['paint']();

    This should crash but it does print "paint" in the console. This demonstrates that you have clearly modified Animal.prototype, which is bad.

    My original code worked around this by copying all prototypes into a new one (the X class). The only drawback is that the base ctor was not called but this is easily fixable. Here is a better mixin function:

    function Mixin<T>(...mixins) : new() => T {
        class X {
            constructor() {
                mixins[0].call(this);
            }
        }
        Object.assign(X.prototype, ...mixins.map(m => m.prototype));
        return <any>X;
    }

    The first argument is considered to be a base class, and hence, its ctor is called.
    You can tweak this in various ways: removing Object.assign (I use it for convenience), call all ctors in chain including your mixins (so that your mixins may initialize state), make the function work without a base class at all, etc.

    Here's an updated playground that demonstrate this working with a non-trivial base class:
    Playground

    function Mixin<T>(...mixins) : new() => T {
        class X {
            constructor() {
                mixins[0].call(this);
            }
        }
        Object.assign(X.prototype, ...mixins.map(m => m.prototype));
        return <any>X;
    }
    
    abstract class Dog {
        bark() { console.log('bark!'); }
    }
    
    abstract class Duck {
        quack() { console.log('quack!'); }
    }
    
    class Base {
        constructor() {
            console.log('base ctor');
        }
    
        world = "world";
    
        hello() {
            console.log(`hello ${this.world}`);
        }
    }
    
    interface DoggyDuck extends Base, Dog, Duck { }
    
    class Mutant extends Mixin<DoggyDuck>(Base, Dog, Duck) {
    
    }
    
    let xavier = new Mutant();
    xavier.bark();
    xavier.quack();
    xavier.hello();
  20. Izhaki commented on Jan 20, 2016

    @Izhaki

    jods (@jods4), well spotted. This is brilliant stuff.

  21. RyanCavanaugh commented on Jan 21, 2016

    @RyanCavanaugh
    Member

    In the interests of not reading this thread in detail if possible -- does the code above solve your use case enough that we can close this as having a sufficient solution in place?

  22. xmehaut commented on Jan 21, 2016

    @xmehaut
    Author

    It is ok for me, even if i would prefer a syntactic sugar in typescript above this solution.
    Best regards

    Envoyé de mon iPhone

    Le 21 janv. 2016 à 03:03, Ryan Cavanaugh notifications@github.com a écrit :

    In the interests of not reading this thread in detail if possible -- does the code above solve your use case enough that we can close this as having a sufficient solution in place?

    —
    Reply to this email directly or view it on GitHub.

  23. kitsonk commented on Jan 23, 2016

    @kitsonk
    Contributor

    Sorry, I missed this... I have used the pattern mentioned in this issue quite extensively and there is one challenge to it (which I don't think the original proposal addressed either, given the following:

    class A {
        foo: string = '';
    }
    
    class B {
        foo() { return 'foo'; };
    }
    
    interface GenericClass<T> {
        new (...args: any[]): T;
    }
    
    function mixin<T, U>(target: GenericClass<T>, source: GenericClass<U>): GenericClass<T&U> {
        return;
    }
    
    const C = mixin(A, B);
    
    const c = new C();
    
    c.foo; // is of type string & (() => string)

    You run into issues when you have members that conflict.

    As far as the original proposal, I would prefer the #3870 as I think that type of operations that we are talking about could be handled better that way something like:

    function mixin<T, ...U>(target: (new () => T), ...mixins: (new () => U)[]): (new () => T & ...U) {
        // do mixin
    }

    There are some other considerations too about that discussed in #3870... should ...U be A&B&C... or should it be A|B|C..., etc...

  24. iBasit commented on Feb 14, 2016

    @iBasit

    A&B&C... makes more since then A|B|C...

  25. silkentrance commented on Mar 4, 2016

    @silkentrance

    Why don't you just use a mixin decorator?

    function mixin(...ifaces) {
    ...
    }
    
    @mixin(Dog, Cat, Mouse)
    class MouseyCatDog {
    }
    

    This would not require any change to the language at all.

  26. kitsonk commented on Mar 4, 2016

    @kitsonk
    Contributor

    Carsten Klein (@silkentrance) run-time decorators don't modify the type information. class MouseyCatDog would be a class without any properties at design time, though it could have the proper run-time structure, but not having the right design time structure is essentially useless (and dangerous).

  27. silkentrance commented on Mar 4, 2016

    @silkentrance

    Kitson Kelly (@kitsonk) Basically, what you are asking for is not a mixin concept but, simply put, a multiple inheritance concept where the first class inherited from would be the actual super prototype and any other classes following that would be treated as mixins, e.g.

    class Foo extends Bar, Car, Tar
    {}
    

    The generated/augmented constructor would then call upon first Bar.apply(), then Car and last but not least Tar.

    If this be true, then instanceof Car and instanceof Tar should not fail. Again, here the transpiler could replace uses of instanceof by a custom implementation that will return true for all mixins and theirs and the super class' prototype chain.

    As such, and during runtime, the generated class Foo would need some extra annotations, e.g.

    Foo[Symbol['inheritanceChain')] = [Bar, Car, Tar];
    

    Otherwise we talk about IDEs here and their use of the AST produced by the typescript ast generator.

    And, according to your last comment on #2900, the use of extraneous files.

  28. ahejlsberg commented on Jan 30, 2017

    @ahejlsberg
    Member

    Closing as mixin classes are now supported with #13743.

  29. 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

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions