-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Comments
how is that diffrent from: class MyGenericClass <aClass> extends aClass {
} |
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
|
One example of use : Envoyé de mon iPhone
|
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 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.". 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 |
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 : It is quite concise and auto explained Envoyé de mon iPhone
|
Hi jods Envoyé de mon iPhone
|
@xmehaut |
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 |
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
|
it is the same purpose , yes, and the syntax is a little bit different 2016-01-19 20:34 GMT+01:00 Mohamed Hegazy notifications@github.com:
|
Can you provide a working example on http://www.typescriptlang.org/Playground ? |
@Izhaki Something along those lines. Note that I simplified the Mixin signature a bit. 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(); |
@jods4 This is outright genius! I've taken the liberty to:
I suspect 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? |
very nice! 2016-01-20 16:25 GMT+01:00 Izhaki notifications@github.com:
|
@Izhaki No problem with the changes, my code was a quick proof of concept ;) Just one point: Aren't you mutating your base classe ctor? |
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 |
@Izhaki I am not sure what you mean by "it works". let bug = new Animal();
bug['paint'](); This should crash but it does print "paint" in the console. This demonstrates that you have clearly modified My original code worked around this by copying all prototypes into a new one (the 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. Here's an updated playground that demonstrate this working with a non-trivial base class: 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(); |
@jods4, well spotted. This is brilliant stuff. |
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? |
It is ok for me, even if i would prefer a syntactic sugar in typescript above this solution. Envoyé de mon iPhone
|
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 |
|
Why don't you just use a mixin decorator?
This would not require any change to the language at all. |
@silkentrance run-time decorators don't modify the type information. |
@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.
The generated/augmented constructor would then call upon first Bar.apply(), then Car and last but not least Tar. If this be true, then As such, and during runtime, the generated class Foo would need some extra annotations, e.g.
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. |
Closing as mixin classes are now supported with #13743. |
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 :
This could be a nice way to define and use mixins...
We may also combine mixins like this :
The text was updated successfully, but these errors were encountered: