-
Notifications
You must be signed in to change notification settings - Fork 9
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
Relationship with the mixins proposal #33
Comments
See also, #29. |
A few months ago, I sent @justinfagnani this gist: https://gist.github.com/michaelficarra/ba35dcd316cd743bcdacd89bf0204390 This proposal has changed a bit since then, but I believe my personal opinion on the matter remains the same. I will try to address the topic during my status update presentation later this month. |
Sorry, I was away getting married when that gist was sent to me and it dropped though the cracks. Getting back to it now. So that gist has a problem with how it describes the desugaring and mixin function, such that it leaves out the main benefit of subclass factories - that they don't copy members, they create a true subclass and therefore all class feature just work, like constructors, super calls, etc. Specifically, this function is incorrect: function mixin(C, ...Ms) {
if (C == null) {
C = class {};
Object.setPrototypeOf(C.prototype, null);
}
return Ms.reduce((superclass, M) => {
class D extends superclass {}
Object.assign(D, M);
Object.defineProperties(D.prototype, Object.getOwnPropertyDescriptors(M.prototype));
return D;
}, C);
} And should be more like: function mixin(C, ...Ms) {
if (C == null) {
C = class {};
Object.setPrototypeOf(C.prototype, null);
}
return Ms.reduce((superclass, M) => M(superclass), C);
} By not copying mixin members, mixins answer some of the open questions on protocols:
Mixins use the prototype chain, so neither copy, nor require internal slots.
super just works with mixins. So do constructors, private fields and static members. |
Thanks for the updates, @michaelficarra and @justinfagnani . I'm looking forward to the update in committee. Ultimately, I hope we can figure out this question before advancing either proposal to Stage 2. |
Stepping back a little bit, I think we can break down the Protocols proposal into three parts (but correct me if this seems wrong):
Mixins only cover the first part. This is intentional too, since they build on classes, any name-spacing or type-checking added to classes would be assumed by mixins. I would argue that mixins do a more complete job of partial class implementations precisely because they are nothing more than subclass factories. If the other two features are deemed important, I think we could address them separately and in a way that also applies to classes, increasing their benefit. For automatic namespacing, I can think of two ways we could achieve something similar: 1: Extra syntax for declaring and using namespaced members: We're running out of symbols to use for sigils to denote that a member declaration should be namespaced, but we still have some syntactic room in the property name. I think would could use a dot-notation to indicate a namespace, where class A {
A.foo() { ... }
}
class B {
A.foo() { ... }
} Would desugar to: let A = (() => {
const A$foo = Symbol();
return class A {
[A$foo]() { ...}
}
})();
class B {
[A.foo]() { ... }
} 2: Use decorators and computed properties. Decorators let use class A {
@namespaced foo() {...}
}
class B {
[A.foo]() { ...}
} For type-checking, that's a much bigger can of worms, but decorators will let us do the shallow type checking pretty easily. A full example with decorators: @abstract
class A {
@abstract
@namespaced
foo() {}
}
@implements(A)
class B {
[A.foo]() {...}
} However those could be accomplished, I do think it would be very beneficial to tease apart the pieces of protocols so they can be applied to classes and mixins. |
+1 to @justinfagnani 's option 2. If decorators reaches Stage 3, let's give ourselves some time with decorators to see if this idiom is sufficient before jumping into making additional syntax for namespacing, whether it's through protocols or some other syntax like 1. |
@justinfagnani 's option 1 would rapidly run into syntax issues. It works if the thing before the dot is just an identifier (or an otherwise reserved word — there are no reserved words in that position!), but you'd need to allow subexpressions there too, for the cases in |
@waldemarhorwat ah, very true. For referencing a namespaced name, a computed property name like |
I'm pretty sure they're exactly the same except your version pushes some boilerplate (the function wrapper and explicit extends) to the user. Contrast: class M0 { /* ... */ }
class M1 { /* ... */ }
class M2 { /* ... */ }
class C extends myMixin(Object, M0, M1, M2) { /* ... */ } with const M0 = S => class M0 extends S { /* ... */ }
const M1 = S => class M1 extends S { /* ... */ }
const M2 = S => class M2 extends S { /* ... */ }
class C extends yourMixin(Object, M0, M1, M2) { /* ... */ } The resulting Regarding an entirely decorators-based implementation, I think this is probably possible but would be less elegant and lead to lower adoption. If we feel this paradigm is important enough to encourage its use, we should give it its own space in the language. |
@michaelficarra those examples are incorrect though. In the mixin pattern and the mixin proposal, mixins are not classes - they are subclass factory functions and return new subclasses each invocation. The reason they aren't classes is that we don't want copy semantics, as that breaks the prototype chain and the ability to use The examples in today's syntax, without the proposal, would be: const M0 = (superclass) => class extends superclass {
foo() { console.log('M0.foo'); }
}
const M1 = (superclass) => class extends superclass {
foo() {
super.foo();
console.log('M1.foo');
}
}
class C extends M1(M0(Object)) {
foo() {
super.foo();
console.log('C.foo');
}
}
const c = new C();
c.foo(); Which prints:
With the
This is why it's critically important that in any return Ms.reduce((superclass, M) => M(superclass), C); This is also why mixins in the proposal are actual functions, and With the mixins proposal the example would be: mixin M0 {
foo() { console.log('M0.foo'); }
}
mixin M1 {
foo() {
super.foo();
console.log('M1.foo');
}
}
class C Object with M0, M1 {
foo() {
super.foo();
console.log('C.foo');
}
} |
@justinfagnani's mixins proposal fits somewhere in the same problem space as protocols, but presents a very different mechanism. How should we decide between these two proposals, something in the middle, or both coexisting?
The text was updated successfully, but these errors were encountered: