Created
November 1, 2011 21:48
-
-
Save genericallyloud/1332028 to your computer and use it in GitHub Desktop.
Unified class, <|, super, mixins proposal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// My take on unifying 3 concepts we currently have on the table: | |
// minimalist classes, triangle operator, and traits. This is mostly | |
// a rehash of existing ideas, with my own twist. Also - I will say upfront | |
// that the traits I'm proposing are for much simpler style ruby mixins | |
// because the traits proposal now are off the table. | |
// To start with, I'd like to advocate the minimalist approach that | |
// Jeremy Ashkenas proposed - the key element being (to me) being that | |
// it can take any expression for its definition. This is causing some | |
// debate right now, but I'll put that on the stack at the moment | |
// ************************** | |
// PART I - minimal classes | |
// ************************* | |
// Jeremy's initial example with slight modification to use some of the | |
// new object literal extensions | |
class Color { | |
constructor(hex) { | |
... | |
}, | |
r: 1, g: 1, b: 1, | |
copy(color) { | |
... | |
}, | |
setRGB(r, g, b) { | |
... | |
}, | |
setHSV(h, s, v) { | |
... | |
} | |
} | |
// This is not so different from what has been proposed before but he goes | |
// on to include the expression forms: | |
class Student objectContainingStudentProperties | |
// Or even: | |
class Protester merge(YoungAdult, WorkEthic, Idealism, { | |
student: true | |
}) | |
// To break this down, I would say that what is going on here is pretty interesting. | |
// class is effectively a special operator that does the magic of a constructor function | |
// in reverse, which strikes me as quite elegant. Sort of like the mathematical dual of new. | |
// where a constructor function | |
function Color(hex){ | |
... | |
} | |
// creates a prototype and automatically maps a constructor property to it | |
// pointing back at the constructor function, | |
class Color { | |
constructor(hex){ | |
... | |
} | |
} | |
// takes an object with a constructor property and creates a constructor | |
// function from it with the object as prototype | |
// one really nice side effect is that it can take the oExemplar form and | |
// convert it into the fExemplar form. This reduces the need for | |
// the new <Object> proposal, although I still like that form. | |
// ***************************************** | |
// PART II - extension and the <| operator | |
// ***************************************** | |
// It seems to me that the extends from class, and the <| operator | |
// are clearly related, and also that nobody likes the syntax of the | |
// <| operator right now. I would propose that we bring the two together | |
// Let me start with the beginning of the exemplar discussion. | |
// Allen provided these examples: | |
// Example A: | |
var SubFoo = Foo <| function (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
}; | |
// Example B: | |
var SubBar = Bar <| { | |
z: 0, | |
constructor (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
} | |
} | |
// and even the possible combination of forms: | |
// Example C: | |
var SubFooLit = Foo <| { | |
z: 0, | |
constructor (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
} | |
} | |
//Example D: | |
var SubBarFunc = Bar <| function (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
}; | |
// Personally, I find the most compelling cases here to be B and C, | |
// The ones with an object literal on the RHS. I find it pretty rare | |
// to ever want to extend a class just to override its constructor function. | |
// The problem is that the result of <| is the RHS, so these will both be object. | |
// You are probably seeing where I'm going with this. If we allow Jeremy's proposal of | |
// class expressions taking arbitrary expressions for the definition, we can bring these two | |
// together. | |
// Alternate Example C: | |
class SubFoo (Foo <| { | |
z: 0, | |
constructor (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
} | |
}) | |
// Thats pretty close, but lacks a little in the way of sugar. | |
// Jeremey's proposal (among others) would look like | |
class SubFoo extends Foo { | |
z: 0, | |
constructor (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
} | |
} | |
// To me, this is just a change from | |
sup <| sub | |
// To | |
extends sup sub | |
// and that is what I would propose as the final syntax for <| | |
// so if we wanted to retain the original oExemplar result we could do | |
var SubFooLit = extends Foo { | |
z: 0, | |
constructor (a,b,c) { | |
super.constructor(a,b); | |
this.z = c; | |
} | |
} | |
// Finally, in regards to extends - to fully match Jeremy's proposal and | |
// be conceptually consistent, <| (or extends) needs to be able to take | |
// an expression on the RHS. Again, I will ask to put this on the stack | |
// because it seems to me that things are fitting into place pretty well. | |
// Note: I understand that the original purpose of <| was to be able to | |
// the prototype of a fresh literal expression, and that this purpose makes | |
// less sense with non-literals, but I think that changing it to be a more arbitrary | |
// extends operator would still work for the original purpose, even if the | |
// semantics differ a little. More on this in Part V | |
// ******************** | |
// PART III - super | |
// ******************** | |
// The discussion on Jeremy's proposal quickly turned into a discussion | |
// on dynamic vs static super. The case has been made for static, and I think that | |
// is fair enough. Jeremy has stated, and others agree, that super should not be tied to | |
// to classes, however, IMHO, I think it would make sense to be tied to extends. I know | |
// that Jeremy would like it to work with any prototypal hierarchy, but considering both | |
// extends and super are new, we don't have to worry about backwards compatibility. Also, | |
// if extends can take arbitrary expressions, then the new mechanism can be added to older | |
// libraries, and used in the same way. | |
// ************************* | |
// PART IV - traits/mixins | |
// ************************* | |
// Traits have been discussed recently, and that discussion along with Mark's(?) original | |
// proposal point to the traits.js library. It has been pointed out that while those traits | |
// are more robust than simple mixins like ruby. I'm pretty sure there's no way | |
// that traits.js style traits will make it into ES6, but what about the simpler mixins. Something | |
// like what Ruby has. It seems to me, that if we can crack the extends RHS-as-expression nut, then | |
// we would basically get mixins for free. | |
const Comparable = { | |
compareTo(other){ | |
throw new Error("Must implement compareTo"); | |
} | |
equals(other){ | |
return this.compareTo(other) === 0; | |
} | |
greaterThan(other){ | |
return this.comparTo(other) > 0; | |
} | |
... | |
} | |
class Person extends Comparable { | |
constructor(name,height){ | |
this.name = name; | |
this.height = height; | |
} | |
compareTo(other){ | |
return this.height - other.height; | |
} | |
} | |
let bill = new Person("Bill", 68); | |
let bob = new Person("Bob",72); | |
let billTaller = bill.greaterThan(bob);//false | |
// This is a simple example and does not require an arbitraty RHS expression, but let's | |
// imagine that we want to use Comparable more like a mixin. Lets say we would like to | |
// also extend a real superclass, but also mixin Comparable and Enumerable | |
class Sub extends Sup extends Comparable extends Enumerable { | |
... | |
} | |
// This would be impossible to do without arbitrary RHS expressions, but just happens if we allow it. | |
// Not sure if we'd want to sugar it up like Scala does: | |
class Sub extends Sup with Comparable, Enumerable { | |
... | |
} | |
// I use with here, but that would obviously clash a little with the existing with - perhaps just ,s | |
class Sub extends Sup, Comparable, Enumerable { | |
... | |
} | |
// *************************** | |
// PART V - non-literal RHS | |
// *************************** | |
// I've put this on the stack because I'm sure it'll be the part that gets the most argument. I'm | |
// hoping that if we can assume for a moment that this problem were solved that most of what I've | |
// proposed would be agreeable to many. Here goes nothing: | |
// I propose a new form of object wrapper that holds an object and a reference to another object | |
// that will act as that object's prototype. This would be strictly non-destructive, but allow it to | |
// be used in place of the original object. | |
// lets start with a <| example | |
function customExtend(parent, child){ | |
//some special modification to child goes here | |
return parent <| child; | |
} | |
// or my proposed form | |
function customExtend(parent, child){ | |
//some special modification to child goes here | |
return extends parent child; | |
} | |
// This would not currently work with <| and for good reason. | |
// With the current semantics, setting the _proto_ for child | |
// could be a BAD thing, and certainly would not work with my intended | |
// semantics for traits/mixins | |
// My idea is that instead of directly modifying child's _proto_, something like a special | |
// purpose direct proxy were used instead, where all method calls pass through to the wrapped object, | |
// but any references to the prototype, or going up the prototype chain would instead be delegated | |
// to the wrapper's specified prototype instead. This would also be what calls to super would refer to, | |
// as the two are directly related. | |
// I'm not sure if this would be easy to make efficient. My gut says it would be more efficient than | |
// copying object properties, and certainly seems like it would alleviate the concerns of things like | |
// copying private members. It would also ensure that in the mixin example, changes to the mixin would | |
// be reflected in the child. Not sure if that is a good thing, but its consistent with prototypal | |
// inheritance. | |
// In the common case of a literal RHS, this wrapper could be elided, and therefore create no | |
// additional overhead. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment