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

Proposal : rest instance variables #6502

Closed
xmehaut opened this issue Jan 15, 2016 · 27 comments
Closed

Proposal : rest instance variables #6502

xmehaut opened this issue Jan 15, 2016 · 27 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@xmehaut
Copy link

xmehaut commented Jan 15, 2016

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 ;
@mhegazy
Copy link
Contributor

mhegazy commented Jan 16, 2016

how is that diffrent from:

class MyGenericClass <aClass> extends aClass {
}

@xmehaut
Copy link
Author

xmehaut commented Jan 16, 2016

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.

@xmehaut
Copy link
Author

xmehaut commented Jan 16, 2016

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.

@jods4
Copy link

jods4 commented Jan 18, 2016

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.

@xmehaut
Copy link
Author

xmehaut commented Jan 19, 2016

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.

@xmehaut
Copy link
Author

xmehaut commented Jan 19, 2016

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.

@jods4
Copy link

jods4 commented Jan 19, 2016

@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 :(

@mhegazy
Copy link
Contributor

mhegazy commented Jan 19, 2016

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.

@xmehaut
Copy link
Author

xmehaut commented Jan 19, 2016

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.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 19, 2016

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

@xmehaut
Copy link
Author

xmehaut commented Jan 19, 2016

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

@mhegazy mhegazy added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jan 20, 2016
@Izhaki
Copy link

Izhaki commented Jan 20, 2016

@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 ?

@jods4
Copy link

jods4 commented Jan 20, 2016

@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();

@Izhaki
Copy link

Izhaki commented Jan 20, 2016

@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<T>( 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<AnimalPainter>( 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();

@mhegazy, I reckon this is much better than the current mixin strategy in the handbook, as it removes the redeclaration issue. Your thoughts?

@xmehaut
Copy link
Author

xmehaut commented Jan 20, 2016

very nice!

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

@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();

@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)
.

@jods4
Copy link

jods4 commented Jan 20, 2016

@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).

@Izhaki
Copy link

Izhaki commented Jan 20, 2016

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

@jods4
Copy link

jods4 commented Jan 20, 2016

@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();

@Izhaki
Copy link

Izhaki commented Jan 20, 2016

@jods4, well spotted. This is brilliant stuff.

@RyanCavanaugh
Copy link
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?

@xmehaut
Copy link
Author

xmehaut commented Jan 21, 2016

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.

@kitsonk
Copy link
Contributor

kitsonk commented Jan 23, 2016

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

@iBasit
Copy link

iBasit commented Feb 14, 2016

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

@silkentrance
Copy link

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.

@kitsonk
Copy link
Contributor

kitsonk commented Mar 4, 2016

@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).

@silkentrance
Copy link

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

@ahejlsberg
Copy link
Member

Closing as mixin classes are now supported with #13743.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants