Minimalist Classes
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
I like the philosophy behind it (as stated at the end of the file).
Question: If the RHS can be any expression, e.g.
class Student objectContainingStudentProperties
Would extends work, too?
class Student extends Human objectContainingStudentProperties
That could be a problem (grammatically) if Human could also be replaced by any expression.
What is your take on object exemplars?
Axel
This is very similar to what Dave Herman, Sam Tobin-Hochstadt and I wrote at the whiteboard after the meeting was over at the last face to face meeting.
I like this pattern too but at this point we are stuck at the following:
Thanks for the proposal. I've been advocating a minimalist approach to classes for a while now; I think it's a good goal. A few of us sketched out something similar on a whiteboard in the last face-to-face meeting; at least, it used the object literal body. We hadn't thought of two of your ideas:
- allowing class expressions to be anonymous
- allowing the RHS to be an arbitrary expression
I like #1; we have a lot of agreement around classes being first-class, and that just fits right in.
But with #2 I'm not clear on the intended semantics. You say this could be desugared but don't provide the details of the desugaring. The RHS is an arbitrary expression that evaluates to the prototype object (call it P), but the extends clause is meant to determine P's [[Prototype]], right? Do you intend to mutate P's [[Prototype]]? I wouldn't agree to that semantics.
Another thing that this doesn't address is super(). Right now in ES5 and earlier, it's pretty painful to call your superclass's constructor:
class Fox extends Animal {
constructor: function(stuff) {
Animal.call(this, stuff);
...
}
}
In general, I think your arbitrary-expression-RHS design is incompatible with the super keyword, which needs to be lexically bound to its class-construction site. I'll have to think about it, though.
However, I think the approach of an object literal body (and only an object literal body) works well. I suspect you could still implement all your examples of dynamically-computed classes, although probably with a little more work in some cases.
On Mon, Oct 31, 2011 at 10:13 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:
I like this pattern too but at this point we are stuck at the following:
Right, but stuck is just a state of mind. ;)
To re-quote:
- Don't let uninitialized objects escape
- Ensure the shape of the instance
- Initialization of instance properties
- Allow const classes
- Allow const properties
- Play well with future type guards
All IMHO:
- Not any more of a concern here than it would be for any other function ... but perhaps I don't understand the point being made.
- Goes completely against the grain of any dynamic language, and JavaScript especially. Let Dart explore this terrain, JS.next doesn't need to.
- Initialization of instance properties happens inside the constructor, as in ES3.
- I'd rather not have const, but if we do, certainly you can const a class, as a class is just an expression like any other.
- I'd rather not have const, but if we do, certainly you can const class properties, because they're values in an object literal just like any other.
- I'd rather not have type guards, but again, if they work with object literals, they'll work with this seamlessly.
Those 6 recurring problems really don't seem too bad. If you build classes out of more fundamental syntactic building blocks (objects and prototypes) ... all of the pieces that are being developed orthogonally for JS.next should just fit together.
A real issue on the other hand, would be the question of how to specify "bound" functions within a class definition, as JS' dynamic this makes that much harder to settle than the rest of these.
Hey Dave.
On Mon, Oct 31, 2011 at 10:28 PM, David Herman <dherman at mozilla.com> wrote:
But with #2 I'm not clear on the intended semantics. You say this could be desugared but don't provide the details of the desugaring. The RHS is an arbitrary expression that evaluates to the prototype object (call it P), but the extends clause is meant to determine P's [[Prototype]], right? Do you intend to mutate P's [[Prototype]]? I wouldn't agree to that semantics.
To be a little clearer on the mechanism, it's the basic pattern you get from most libraries that do this sort of thing. We can use goog.inherits as the canonical example: closure-library.googlecode.com/svn/docs/closure_goog_base.js.source.html#line1421
class Fox extends Animal { dig: function() {} }
Fox becomes a constructor function with a .prototype
that is set to an
instance of Animal that has been constructed without calling the Animal()
constructor. (The usual temporary-constructor-to-hold-a-prototype two step
shuffle). All of the own properties from the RHS are merged into the empty
prototype. The only mutation here is of a brand new object. Animal is not a
prototype here, it's a class (constructor function) in its own right.
Another thing that this doesn't address is super(). Right now in ES5 and earlier, it's pretty painful to call your superclass's constructor:
In general, I think your arbitrary-expression-RHS design is incompatible with the super keyword, which needs to be lexically bound to its class-construction site. I'll have to think about it, though.
super() is a separate (but very much needed) issue -- that should still make it in to JS.next regardless of if a new class syntax does. Likewise, super() calls should not be limited only to objects that have been build with our new classes, they should work with any object that has a prototype (a proto).
Depending on whether you implement it as a reference to the prototype, or a reference to the prototype's implementation of the current function -- an approach that I would prefer -- the general scheme is:
Take the current object's proto and apply the method of the same name there against the current object. If you know the name of the property, and you have the reference to the prototype, I don't see why this would preclude dynamic definitions.
class Fox extends Animal { dig: function() {} }
Fox becomes a constructor function with a
.prototype
that is set to an instance of Animal that has been constructed without calling the Animal() constructor. (The usual temporary-constructor-to-hold-a-prototype two step shuffle). All of the own properties from the RHS are merged into the empty prototype. The only mutation here is of a brand new object.
OK.
Animal is not a prototype here, it's a class (constructor function) in its own right.
Sure, I just didn't realize you wanted copying. I thought you were doing something like
P.__proto__ = SuperClass.prototype;
But IIUC, you're proposing a semantics where you construct a brand new object P whose proto is SuperClass.prototype and then copy all the own-properties of the RHS into P. That's fine as far as it goes, but super still doesn't work (see below).
Another thing that this doesn't address is super(). Right now in ES5 and earlier, it's pretty painful to call your superclass's constructor:
In general, I think your arbitrary-expression-RHS design is incompatible with the super keyword, which needs to be lexically bound to its class-construction site. I'll have to think about it, though.
super() is a separate (but very much needed) issue -- that should still make it in to JS.next regardless of if a new class syntax does. Likewise, super() calls should not be limited only to objects that have been build with our new classes, they should work with any object that has a prototype (a proto).
I wish it were possible, but Allen has convinced me that you can't make super work via a purely dynamic definition. It has to be hard-wired to the context in which it was created. Let me see if I can make the argument concisely.
Semantics #1: super(...args) ~=~ this.proto.METHODNAME.call(this.proto, ...args)
This semantics is just wrong. You want to preserve the same |this| as you move up the chain of super calls, so that all this-references get the most derived version of all other properties.
Semantics #2: super(...args) ~=~ this.proto.METHODNAME.call(this, ...args)
This semantics is just wrong. It correctly preserves the most derived |this|, but if it tries to continue going up the super chain, it'll start right back from the bottom; you'll get an infinite recursion of the first super object's version of the method calling itself over and over again.
Semantics #3: super(...args) ~=~ this.proto.METHODNAME.callForSuper(this, this.proto, ...args)
This semantics introduces a new implicit "where did I leave off in the super chain?" argument into the call semantics for every function in the language. It's sort of the "correct" dynamic semantics, but it introduces an unacceptable cost to the language. JS engine implementors will not accept it.
Semantics #4: super(...args) ~=~ LEXICALLYBOUNDPROTO.call(this, ...args)
This semantics properly goes up the super chain via lexical references only, so it avoids the infinite recursion of #2. It doesn't introduce any new cost to the call semantics of JS, so it doesn't have the cost of #3.
Depending on whether you implement it as a reference to the prototype, or a reference to the prototype's implementation of the current function -- an approach that I would prefer -- the general scheme is:
Take the current object's proto and apply the method of the same name there against the current object. If you know the name of the property, and you have the reference to the prototype, I don't see why this would preclude dynamic definitions.
I thought the exact same thing when I first thought about super. But sadly, as Allen taught me, this is semantics #2, which leads to infinite super-call recursion.
On Mon, Oct 31, 2011 at 11:11 PM, David Herman <dherman at mozilla.com> wrote:
But IIUC, you're proposing a semantics where you construct a brand new object P whose proto is SuperClass.prototype and then copy all the own-properties of the RHS into P.
Not quite. P is a constructor function (class object), SuperClass is a constructor function. Unless I'm confused, P's ".prototype" is an instance of SuperClass, and therefore instances of P have an RHS own-property-filled proto object that itself has a proto object pointing at SuperClass.prototype. Fun stuff.
I wish it were possible, but Allen has convinced me that you can't make
super work via a purely dynamic definition. It has to be hard-wired to the context in which it was created. Let me see if I can make the argument concisely.
Semantics #1: super(...args) ~=~ this.proto.METHODNAME.call(this.proto, ...args)
This semantics is just wrong. You want to preserve the same |this| as you move up the chain of super calls, so that all this-references get the most derived version of all other properties.
Semantics #2: super(...args) ~=~ this.proto.METHODNAME.call(this, ...args)
This semantics is just wrong. It correctly preserves the most derived |this|, but if it tries to continue going up the super chain, it'll start right back from the bottom; you'll get an infinite recursion of the first super object's version of the method calling itself over and over again.
Semantics #3: super(...args) ~=~ this.proto.METHODNAME.callForSuper(this, this.proto, ...args)
This semantics introduces a new implicit "where did I leave off in the super chain?" argument into the call semantics for every function in the language. It's sort of the "correct" dynamic semantics, but it introduces an unacceptable cost to the language. JS engine implementors will not accept it.
Semantics #4: super(...args) ~=~ LEXICALLYBOUNDPROTO.call(this, ...args)
Indeed, super() is tricky. For what it's worth, CoffeeScript's class syntax requires literal (non-expression) class body definitions in part to make Semantics #4 possible, with purely lexical super calls. Your example's "LEXICALLYBOUNDPROTO" is CoffeeScript's "ClassObject.super".
Fortunately, y'all have the ability to bend the runtime to your will. To
solve the super() problem, you can simply have the JavaScript engine keep
track of when a function has called through the super()
boundary, and
from that point on downwards in that method's future call stack, add an
extra __proto__
lookup to each super resolution. When the inner super()
is then called in the context of the outer this
, the result will be a
variant of #2:
this.proto.proto.METHODNAME.call(this, ...args)
... and it should work.
There may be something wrong with the above -- but dynamic super() should be a solveable problem for JS.next, even if not entirely desugar-able into ES3 terms.
But IIUC, you're proposing a semantics where you construct a brand new object P whose proto is SuperClass.prototype and then copy all the own-properties of the RHS into P.
Not quite. P is a constructor function (class object), SuperClass is a constructor function. Unless I'm confused, P's ".prototype" is an instance of SuperClass,
Oh sorry, we're just miscommunicating about what's labeled what -- I was using P to name the .prototype of the subclass constructor. Let me be concrete:
class SubClass extends SuperClass RHS
I was using P as the name of the new object created and stored as SubClass.prototype. Then P.proto is SuperClass.prototype, and the own-properties of the object that RHS evaluates to are copied into P. Instances O of SubClass have O.proto === P.
and therefore instances of P have an RHS own-property-filled proto object that itself has a proto object pointing at SuperClass.prototype. Fun stuff.
Wheeee...
Indeed, super() is tricky. For what it's worth, CoffeeScript's class syntax requires literal (non-expression) class body definitions in part to make Semantics #4 possible, with purely lexical super calls. Your example's "LEXICALLYBOUNDPROTO" is CoffeeScript's "ClassObject.super".
Fortunately, y'all have the ability to bend the runtime to your will. To solve the super() problem, you can simply have the JavaScript engine keep track of when a function has called through the
super()
boundary, and from that point on downwards in that method's future call stack, add an extra__proto__
lookup to each super resolution. When the innersuper()
is then called in the context of the outerthis
, the result will be a variant of #2:this.proto.proto.METHODNAME.call(this, ...args)
... and it should work.
This doesn't sound right to me. What happens if you call the same method on another object while the super-resolution is still active for the first call? IOW, this sounds like it has similar problems to dynamic scope; the behavior of a function becomes sensitive to the context in which it's called, which is unmodular.
There may be something wrong with the above -- but dynamic super() should be a solveable problem for JS.next, even if not entirely desugar-able into ES3 terms.
The problem isn't so much whether it's possible to come up with a semantics by changing the runtime; I'm sure we could do that. The problem is finding a way to get the semantics you want without taxing the performance all other function calls in the language. (Also known as a "pay-as-you-go" feature: if you don't use the feature, it shouldn't cost you anything.) We don't know how to do that for super().
So I guess in theory I agree it'd be nice if super() and class could be designed completely orthogonally, but in practice they affect each other. But at the same time, I think a class syntax where the body is restricted to be declarative is actually a nice sweet spot anyway. You can still dynamically create classes just like always, but the declarative form gives you a sweet and simple syntax for the most common case.
While the class literal proposed by Jeremy is indeed nice, I think it misses the point of why class literals are desirable. What you've proposed can already be achieved, with the same "sugar" -- and without introducing a new construct, so people using ES3 can also get it through a library, etc -- by using object exemplars: Selfish, Prototoypes As Classes, etc.
The e-mail Erik pointed has got most of it, though what I really think
class literals can bring in that makes them worth is a better and
declarative syntax for private/const members and traits. Static shape can
be achieved through Object.freeze', right? I don't think that should be what classes strive to provide. Yeah, static analysis is nice and all, but I believe that goes against the nature of the language's own semantics, and if classes are supposed to be just sugar over prototypical inheritance (with the added benefits I mentioned previously), it makes more sense that they don't make such shape guarantees, imho -- vendors could still optimise for static shape, and fallback to non-optimised code if the instance's shape change, which I guess is what is done today with prototypes(?) and which I think should also be done for
with', but I digress.
Still straying a bit from the main subject here, I thought type guards were supposed to be defined at the function level, no? Or is it that the shape of classes would be used to determine the types used?
2011/11/1 David Herman <dherman at mozilla.com>
There may be something wrong with the above -- but dynamic super() should be a solveable problem for JS.next, even if not entirely desugar-able into ES3 terms.
The problem isn't so much whether it's possible to come up with a semantics by changing the runtime; I'm sure we could do that. The problem is finding a way to get the semantics you want without taxing the performance all other function calls in the language. (Also known as a "pay-as-you-go" feature: if you don't use the feature, it shouldn't cost you anything.) We don't know how to do that for super().
So I guess in theory I agree it'd be nice if super() and class could be designed completely orthogonally, but in practice they affect each other. But at the same time, I think a class syntax where the body is restricted to be declarative is actually a nice sweet spot anyway. You can still dynamically create classes just like always, but the declarative form gives you a sweet and simple syntax for the most common case.
Dave
Yes, |super| is static to avoid the cost of passing an additional parameter to every function call, whether that uses |super| or not. Object.defineMethod is provided to make functions with bound |super| to other objects. I also think that dynamic |super| makes more sense with JavaScript, but vendors are concerned about performance -- plus, Object.defineMethod should cover the other cases, albeit not as nicely as dynamic |super| would.
This doesn't sound right to me. What happens if you call the same method on another object while the super-resolution is still active for the first call? IOW, this sounds like it has similar problems to dynamic scope; the behavior of a function becomes sensitive to the context in which it's called, which is unmodular.
The problem isn't so much whether it's possible to come up with a semantics by changing the runtime; I'm sure we could do that. The problem is finding a way to get the semantics you want without taxing the performance all other function calls in the language. (Also known as a "pay-as-you-go" feature: if you don't use the feature, it shouldn't cost you anything.) We don't know how to do that for super().
I think one piece of this is worth reiterating: As long as JS.next classes are mostly sugar for prototypes, and prototypes aren't going to be deprecated or removed in the next version of JavaScript (two propositions that I think most of us can get behind) ... then it's very important that super() and new class syntax aren't coupled. An ES6 super() that fails to work at all with regular prototypes would be a serious problem. It would make interoperating between vanilla prototypes and prototypes-built-by-classes much more difficult than necessary, and the feel of the language as a whole much more fragmented.
If you agree, then a super() that resolves dynamically is the way forward, and things get easier: super() can be added without classes, classes can be added without super(), and if both make it in, both can work seamlessly together.
I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible. Expanding the rough sketch from earlier messages:
- If a function doesn't use super(), there is no cost, and no change in semantics.
- The first-level super() call is easy, just use the method of the same name on the proto.
- When passing into a super(), add a record to the call stack that contains [the current object, the name of the method, and the next level proto].
- When returning from a super(), pop the record from the call stack.
- When making a super() call, check the call stack for a record about the current object and method name, and use the provided proto instead of this.proto if one exists.
So I guess in theory I agree it'd be nice if super() and class could be designed completely orthogonally, but in practice they affect each other. But at the same time, I think a class syntax where the body is restricted to be declarative is actually a nice sweet spot anyway. You can still dynamically create classes just like always, but the declarative form gives you a sweet and simple syntax for the most common case.
It's definitely the most common case, but a JavaScript class syntax that is only able to create instances with a static shape would be severely limited compared to current prototypes. Many existing libraries and applications would be unable to switch to such a syntax. One familiar example off the top of my head is Underscore.js:
documentcloud.github.com/underscore/docs/underscore.html#section-127
... particularly the bit about adding all of the functional helpers to the "wrapper" object's prototype, to make chaining style possible.
On the other hand, with the minimalist class proposal in this thread, switching this library over to use them would be simple (if not terribly pretty):
class wrapper _.extend({ constructor: function(obj) { this._wrapped = obj; } }, _)
On 01.11.2011 17:53, Jeremy Ashkenas wrote:
This doesn't sound right to me. What happens if you call the same method on another object while the super-resolution is still active for the first call? IOW, this sounds like it has similar problems to dynamic scope; the behavior of a function becomes sensitive to the context in which it's called, which is unmodular. The problem isn't so much whether it's possible to come up with a semantics by changing the runtime; I'm sure we could do that. The problem is finding a way to get the semantics you want without taxing the performance all other function calls in the language. (Also known as a "pay-as-you-go" feature: if you don't use the feature, it shouldn't cost you anything.) We don't know how to do that for super().
I think one piece of this is worth reiterating: As long as JS.next classes are mostly sugar for prototypes, and prototypes aren't going to be deprecated or removed in the next version of JavaScript (two propositions that I think most of us can get behind) ... then it's very important that super() and new class syntax aren't coupled. An ES6 super() that fails to work at all with regular prototypes would be a serious problem. It would make interoperating between vanilla prototypes and prototypes-built-by-classes much more difficult than necessary, and the feel of the language as a whole much more fragmented.
If you agree, then a super() that resolves dynamically is the way forward, and things get easier: super() can be added without classes, classes can be added without super(), and if both make it in, both can work seamlessly together.
I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible. Expanding the rough sketch from earlier messages:
- If a function doesn't use super(), there is no cost, and no change in semantics.
- The first-level super() call is easy, just use the method of the same name on the proto.
- When passing into a super(), add a record to the call stack that contains [the current object, the name of the method, and the next level proto].
- When returning from a super(), pop the record from the call stack.
- When making a super() call, check the call stack for a record about the current object and method name, and use the provided proto instead of this.proto if one exists.
If Dave (and you) is talking about the problem of i-looping at resolving deeper than 2nd level super-calls, then even call-stack is not needed.
See this implementation: gist.github.com/1330574#L68 (with delete-restore the parent link technique).
Usage level with examples (line 95): gist.github.com/1330574#L95
P.S.: I noticed several times here that this
I think one piece of this is worth reiterating: As long as JS.next classes are mostly sugar for prototypes, and prototypes aren't going to be deprecated or removed in the next version of JavaScript (two propositions that I think most of us can get behind) ... then it's very important that super() and new class syntax aren't coupled. An ES6 super() that fails to work at all with regular prototypes would be a serious problem. It would make interoperating between vanilla prototypes and prototypes-built-by-classes much more difficult than necessary, and the feel of the language as a whole much more fragmented.
I agree that we should avoid introducing things you can do with classes that you can't do with prototypes.
If you agree, then a super() that resolves dynamically is the way forward.
But I disagree with this claim. Allen's triangle operator (which is semantically well-designed but I believe needs a better syntax) gives you just this ability to do super() with prototypes and without classes. And classes are still sugar.
I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible. Expanding the rough sketch from earlier messages:
- If a function doesn't use super(), there is no cost, and no change in semantics.
- The first-level super() call is easy, just use the method of the same name on the proto.
- When passing into a super(), add a record to the call stack that contains [the current object, the name of the method, and the next level proto].
- When returning from a super(), pop the record from the call stack.
- When making a super() call, check the call stack for a record about the current object and method name, and use the provided proto instead of this.proto if one exists.
This approach still gets confused if you recursively call the same method on the same object in the middle of a super-dispatch. Bottom line: it's not equivalent to the behavior where every function call receives an extra argument (and which no one in TC39 knows how to implement in a pay-as-you-go manner).
So I guess in theory I agree it'd be nice if super() and class could be designed completely orthogonally, but in practice they affect each other. But at the same time, I think a class syntax where the body is restricted to be declarative is actually a nice sweet spot anyway. You can still dynamically create classes just like always, but the declarative form gives you a sweet and simple syntax for the most common case.
It's definitely the most common case, but a JavaScript class syntax that is only able to create instances with a static shape would be severely limited compared to current prototypes.
Not at all! Current prototypes have to be created as objects. How do you create an object? Either with an object literal, which has a static shape, or via a constructor call; anything else you do with assignments. With literal classes you have exactly the same options at your disposal.
Many existing libraries and applications would be unable to switch to such a syntax. One familiar example off the top of my head is Underscore.js:
documentcloud.github.com/underscore/docs/underscore.html#section-127
Concrete examples are super duper awesome -- thank you so much for bringing this into the discussion.
...with the minimalist class proposal in this thread, switching this library over to use them would be simple (if not terribly pretty):
class wrapper _.extend({ constructor: function(obj) { this._wrapped = obj; } }, _)
It's true that with literal classes you wouldn't be able to put all the initialization inside the class body, but you could still declare it with a class.
class wrapper {
constructor: function(obj) {
this._wrapped = obj;
}
}
_.extend(wrapper.prototype, /* whatever you like */);
The rest you could do as-is; the difference here is minimal, and IMO it's a good thing to distinguish the fixed structure from the dynamically computed structure. Anyway, JS has plenty of precedent for that.
Dave
PS I also think the "extend" pattern could be done with a cleaner generalization of monocle-mustache:
wrapper.prototype .= _;
But that's fodder for another thread...
The rest you could do as-is; the difference here is minimal, and IMO it's a good thing to distinguish the fixed structure from the dynamically computed structure. Anyway, JS has plenty of precedent for that.
Yep -- we're 98% in agreement about these, and the difference here is indeed quite minimal. Static-bodied classes mean that users have to resort to "prototype" property manipulation knowledge in order to do anything dynamic -- where as Expression-bodies classes mean that the "prototype" property can be elided in most cases. But really, I'd be happy either way.
On Nov 1, 2011, at 6:53 AM, Jeremy Ashkenas wrote:
This doesn't sound right to me. What happens if you call the same method on another object while the super-resolution is still active for the first call? IOW, this sounds like it has similar problems to dynamic scope; the behavior of a function becomes sensitive to the context in which it's called, which is unmodular.
The problem isn't so much whether it's possible to come up with a semantics by changing the runtime; I'm sure we could do that. The problem is finding a way to get the semantics you want without taxing the performance all other function calls in the language. (Also known as a "pay-as-you-go" feature: if you don't use the feature, it shouldn't cost you anything.) We don't know how to do that for super().
I think one piece of this is worth reiterating: As long as JS.next classes are mostly sugar for prototypes, and prototypes aren't going to be deprecated or removed in the next version of JavaScript (two propositions that I think most of us can get behind) ... then it's very important that super() and new class syntax aren't coupled. An ES6 super() that fails to work at all with regular prototypes would be a serious problem. It would make interoperating between vanilla prototypes and prototypes-built-by-classes much more difficult than necessary, and the feel of the language as a whole much more fragmented.
Jeremy, have you looked at the current super proposal at harmony:object_initialiser_super
It does not couple super use with classes at all. It does make it easy to define a super referencing method in an object or class literal, but the mechanism of super can be applied to any method defined in any object.
You are creating confusion by suggesting that the proposed ES6 super support "fails to work at all with regular prototypes".
If you agree, then a super() that resolves dynamically is the way forward, and things get easier: super() can be added without classes, classes can be added without super(), and if both make it in, both can work seamlessly together.
I absolutely agree with your requirement and absolutely disagree with your conclusion regarding "dynamic" super.
The complication of super is that each "super call" requires two independent pieces of state in additional to the method arguments: the object that will be used as the |this| value of the resulting method invocation and the object where property lookup will begin when searching for the method. Let's call this the "lookup-point". |super| when used to access a method property really represents a pair of values (this,lookup-point). In the general case, these are not the same value so they must be independently captured and represented.
Where do these two pieces of state come from? The |this| value of a super call is simply the |this| that was dynamically passed into the calling method. Where does the look-up point come from. There are fundamentally two possibilities:
a) it is dynamically passed to every method invocation, ;just like |this| currently is b) it is statically associated with the method in some way.
Each of these approach has some undesirable characteristics.
a) requires that an extra implicit argument must be passed to every method invocation. The value of this argument is the object from which the method was retrieved as an own property. This extra argument must be passed on every call because in general the call site does not know whether or not the callee references |super| so it must assume that it does. "Most" method calls take a single argument plus the implicit |this| argument (the actual argument statistics vary somewhat among languages but this is a good rule of thumb approximation for all OO languages including JS) so adding an additional lookup-point implicit argument is increasing the argument passing overhead of most calls by 50% yet super is only actually used by a single digit percentage of methods. Also note that after inline caching has been applied, that argument passing is a major part of the total overhead of a call. So, approach a) will have a negative performance impact on all calls for all programs.
b) static associates with each method a reference to it's lookup-poimt. This association is made when the method is "installed" in an object. This installation can occur syntactically, for example by defining a method as part of an object literal. It can occur dynamically, for example, via the Object.defineMethod function in the ES6 proposal. The advantage of this approach is that impose no overhead on method calls. It is complete pay-as-you-go. The disadvantage is that super-referencing methods be bound to a specific object. If you want to copy the method to a different object you have to copy the method and rebind it. Otherwise the method will remain statically bound to its original "home" object.
All modern OO languages that I am aware of have taken path b). This includes highly dynamic prototypal languages such as self. This is also the decision that was made when TC39 discussed this issue. b) is simply the best compromise to a set of conflicting goals.
Just saying "I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible" is not helpful, particular when you make other statement that suggest that you really don't understand all the semantic subtitles of super. I hate to cite "authority" but the semantics and implementation alternatives of super has been thoroughly explored over 30+ years by object-oriented language designer and implementer including myself. This is not a new problem, the "technical chops" have already been supplied. A unique new solution that no one has ever though of before would be welcomed. If you have such a solution, please put it forward. But don't assume that it is something that has not already been deeperly consider both by TC39 member and other language designers.
On Tue, Nov 1, 2011 at 9:45 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
On Nov 1, 2011, at 6:53 AM, Jeremy Ashkenas wrote:
The complication of super is that each "super call" requires two independent pieces of state in additional to the method arguments: the object that will be used as the |this| value of the resulting method invocation and the object where property lookup will begin when searching for the method. Let's call this the "lookup-point". |super| when used to access a method property really represents a pair of values (this,lookup-point). In the general case, these are not the same value so they must be independently captured and represented. Where do these two pieces of state come from? The |this| value of a super call is simply the |this| that was dynamically passed into the calling method. Where does the look-up point come from. There are fundamentally two possibilities: a) it is dynamically passed to every method invocation, ;just like |this| currently is b) it is statically associated with the method in some way.
I'm just curious and I think understanding the following point would help developers:
Why isn't the |super| lookup-point |this.getPrototypeOf()| (The protolink or [[Prototype]])? I thought that if let f = new F(); and F inherits from G, then f.foo() will have this.getPrototypeOf() === G and that is what super would be? Somewhere I went wrong...
jjb
Why isn't the |super| lookup-point |this.getPrototypeOf()|
Assume |super| is |this.getPrototypeOf()|
Let F be a "class", let f be an instance of the class.
inside f you have access to a method defined on F.
If you call a method defined on F from f and that method calls super, you would be invoking getPrototypeOf(this) which is just F (since this is f, and the prototype of f is F). So you would then end up calling the same method again and recurse endlessly.
Basically because you apply methods with a value of this then if super were tied to this you would get infinite super recursion if you chained super calls.
On Nov 1, 2011, at 9:45 AM, Allen Wirfs-Brock wrote:
The complication of super is that each "super call" requires two independent pieces of state in additional to the method arguments: the object that will be used as the |this| value of the resulting method invocation and the object where property lookup will begin when searching for the method. Let's call this the "lookup-point". |super| when used to access a method property really represents a pair of values (this,lookup-point). In the general case, these are not the same value so they must be independently captured and represented. [snip] Just saying "I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible" is not helpful, particular when you make other statement that suggest that you really don't understand all the semantic subtitles of super. I hate to cite "authority" but the semantics and implementation alternatives of super has been thoroughly explored over 30+ years by object-oriented language designer and implementer including myself. This is not a new problem, the "technical chops" have already been supplied. A unique new solution that no one has ever though of before would be welcomed. If you have such a solution, please put it forward. But don't assume that it is something that has not already been deeperly consider both by TC39 member and other language designers.
There can't be a solution if the problem is as stated. We have a pigeon-hole problem with only one implicit ("0th") parameter |this|, but two actuals that must be (in general) passed: (this, lookupPoint).
The solution (b) puts lookupPoint in the [[Super]] internal property of the method's function object, where it can be fetched, and which can be preset for a copy of the function object via Object.defineMethod. Any solution that puts lookupPoint somewhere in the heap reachable from the method is equivalent.
Storing lookupPoint in |this| doesn't work -- pigeon-hole problem again (may have several super calls nesting on the same |this|).
No more time for a formal proof, but it seems to me this is enough. Plan (a) is right out -- no implementor and few users will go for such an increase in argument passing overhead. Plan (b) wins.
If you have a prototype chain of objects, |this| will always point to the beginning of the chain (which is also where properties are created whenever you set a property via |this|). This is what allows methods in the prototype to access instance properties.
If there is a method m in any of the objects of the chain and it makes a super-call, you want the search to start in the prototype of m’s object – and not in the prototype of |this| (which is always the second object in the prototype chain).
Does that make sense?
On 01.11.2011 21:30, Jake Verbaten wrote:
Why isn't the |super| lookup-point |this.getPrototypeOf()|
Assume |super| is |this.getPrototypeOf()|
Let F be a "class", let f be an instance of the class.
inside f you have access to a method defined on F.
If you call a method defined on F from f and that method calls super, you would be invoking getPrototypeOf(this) which is just F (since this is f, and the prototype of f is F). So you would then end up calling the same method again and recurse endlessly.
Basically because you apply methods with a value of this then if super were tied to this you would get infinite super recursion if you chained super calls.
Haven't I just showed a solution for this problem in my previous letter?
This issue is well known for years and have (at least) three working and elegant solutions -- i.e. w/o hardcoding names of classes.
Dmitry.
On 01.11.2011 21:34, Axel Rauschmayer wrote:
If you have a prototype chain of objects, |this| will always point to the beginning of the chain (which is also where properties are created whenever you set a property via |this|). This is what allows methods in the prototype to access instance properties.
If there is a method m in any of the objects of the chain and it makes a super-call, you want the search to start in the prototype of m’s object – and not in the prototype of |this| (which is always the second object in the prototype chain).
Does that make sense?
No it doesn't. Is there someone on the list who reads it carefully?
Once again. This is a well-known for years issue and has several (at least three) solutions. One of them (which I call "delete-and-restore parent link" technique) I showed in the previous letter.
The mentioned technique allows normally to make super calls w/o hardcoding names of classes. Other techniques (such as using caller and wrappers) also can be used, but not so efficient.
Dmitry.
If I am not mistaken, this approach would not work with Function.prototype.call, which lets you pick any method and hand in an arbitrary |this|. That means you skip a step in the tracking that you are performing.
On 01.11.2011 21:41, Axel Rauschmayer wrote:
If I am not mistaken, this approach would not work with Function.prototype.call, which lets you pick any method and hand in an arbitrary |this|. That means you skip a step in the tracking that you are performing.
Can you show an example (and also the same example which is solved by es-proposed super-calls)?
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
Dmitry.
On Nov 1, 2011, at 10:47 AM, Dmitry Soshnikov wrote:
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
We're not going to delete and restore. That's a non-starter for performance and observable mutation reasons.
If you think something can be adapted, you have to show how. At this point we don't need more assertions.
I'm afraid that this super() discussion has gotten way off topic from the actual minimalist classes thread (which would be much more fun to talk about) -- but in the interest of dynamic class bodies, let's run it down.
On Tue, Nov 1, 2011 at 12:45 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
Jeremy, have you looked at the current super proposal at harmony:object_initialiser_super
I hadn't looked at the current super proposal in great depth, but thanks for the link.
It does not couple super use with classes at all. It does make it easy to define a super referencing method in an object or class literal, but the mechanism of super can be applied to any method defined in any object.
You are creating confusion by suggesting that the proposed ES6 super support "fails to work at all with regular prototypes".
Browsing through the examples on the page, the current super proposal does seem very coupled to "classes" ... by in this case being coupled to your proposed <| operator. If you try to use the proposed super with current standard prototype patterns, this happens:
Fox.prototype.dig = function() { super.dig(); };
(new Fox).dig(); TypeError: Object #<Object> has no method 'dig'
Likewise, for the other usual way of setting a prototype:
Fox.prototype = { dig: function() { super.dig(); } };
(new Fox).dig(); TypeError: Object #<Object> has no method 'dig'
In the first case, the internal [[Super]] property is null. In the second case, the internal [[Super]] property is Object.prototype. Both cases will be quite frustrating for most JavaScript programmers.
Naturally, use of the <| operator instead of ".prototype" fixes this, but that coupling of the two syntaxes was the point I was trying to make. There is also Object.defineMethod() ... which would work for setting functions-that-use-super on a prototype imperatively, but that's far too verbose of a solution to propose for the common case. All that I'm trying to say is that the super() that JS.next adopts would do well to continue to work properly in the two examples above.
Just saying "I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible" is not helpful, particular when you make other statement that suggest that you really don't understand all the semantic subtitles of super. I hate to cite "authority" but the semantics and implementation alternatives of super has been thoroughly explored over 30+ years by object-oriented language designer and implementer including myself. This is not a new problem, the "technical chops" have already been supplied. A unique new solution that no one has ever though of before would be welcomed. If you have such a solution, please put it forward. But don't assume that it is something that has not already been deeperly consider both by TC39 member and other language designers.
I hear you loud and clear, and would only be too happy to get off your lawn ;) All that I can cite is an utter lack of authority, and little experience with even undergraduate computer science -- there are very few assumptions here on my part. That said, if ES-Discuss wants to be an open forum, sometimes the rabble gets to speak up and be ignorant.
So, speaking from ignorance, here's a simple proof of concept in ES3 of how dynamic super calls can work with a little bit of metadata:
... which prints out:
In the GreatGrandParent. In the GrandParent. In the Parent. In the Child.
In a more ideal implementation, you'd use references to protos instead of counters, and your metadata would be scoped to be specific to an [object, methodName] pair ... but I think that this example is a bit clearer.
Hopefully this solution isn't unique or new, but dynamic languages are powerful things, and I would hope that dynamic super() is a pattern that's not logically impossible or inherently inefficient to implement, given a bit of extra bookkeeping behind the scenes.
On 01.11.2011 21:57, Brendan Eich wrote:
On Nov 1, 2011, at 10:47 AM, Dmitry Soshnikov wrote:
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
We're not going to delete and restore. That's a non-starter for performance and observable mutation reasons.
If you think something can be adapted, you have to show how. At this point we don't need more assertions.
Well, at least I showed that the problem which so loudly sound here (w/o seeing alternative solutions) isn't actually the problem -- even for a library.
When I was showing it, it was just a reaction on the "big" problem issued by Dave and others, it wasn't the proposal "let's standardize it". Of course I as well understand that it's not good for performance.
And the point b) with "statically attached" class for a method, yes, it
also was here for years and can be implemented in any ES3 library (with
using caller
which though is deprecated for user level, super calls
were more than elegant
On Nov 1, 2011, at 10:57 AM, Brendan Eich wrote:
On Nov 1, 2011, at 10:47 AM, Dmitry Soshnikov wrote:
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
We're not going to delete and restore. That's a non-starter for performance and observable mutation reasons.
the mutation is so wrong, and not just for performance reasons.
Assume x is global constant reference to an obj and foo is a method of that object that does a super.foo call up through several levels of "superclass" foo methods.
In that case the expression "x.foo()" when called from "somewhere else, such as from a top level expression", is going to do something different then what it does when called (either directly or indirectly) from a super invocation of one of the superclass foo methods. That can't be reasonable behavior.
Can you show an example (and also the same example which is solved by es-proposed super-calls)?
let A = { describe() { return "A"; } } let B = A <| { describe() { return "B"+super.describe(); } } let C = B <| { describe() { return "C"+super.describe(); } } let obj = C <| {};
Invocation: B.describe.call(obj) should return "BA". With your library I would expect it to return "BBA".
Furthermore, I don’t think your approach would work here:
let A = { one() { return this.two(); } two() { return "a"; } } let B = A <| { one() { return "B"+super.one(); } two() { return "b"+super.two(); } } let C = B <| { one() { return "C"+super.one(); } one() { return "b"+super.two(); } } let obj = C <| {};
Would obj.one() work? As far as I can tell, your bookkeeping works for one super recursion only, not for two.
On Tue, Nov 1, 2011 at 2:21 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
Invocation: B.describe.call(obj) should return "BA". With your library I would expect it to return "BBA".
Yes, it would. That's a good tricky case. I'm not sure if extra bookkeeping could make sense of that call, as there isn't any information as to where the function fits in to obj's prototype chain -- or if it's part of another prototype chain entirely. I'd like to hope that something could be figured out, but another alternative is for super() within a direct call() or apply() to be an error.
Would obj.one() work? As far as I can tell, your bookkeeping works for one super recursion only, not for two.
Yes, obj.one() would work. You can keep track of the super() depth per-object-per-method ... and even for fancier cases where you restart the same super() chain recursively from the outside. Imagine if one of the deeper super.one()'s itself calls obj.one(), with a direct reference to the instance (and a different parameter, to avoid infinite recursion). You can imagine super counters as a stack instead of a value, and push a fresh one on, popping it when the new obj.one() call exits.
Can you show an example (and also the same example which is solved by es-proposed super-calls)?
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
Code taken from gist.github.com/1330574#L68
Object.defineProperty(Object.prototype, "super", { value: function (method) { let proto = Object.getPrototypeOf(this);
// we should use exactly this link and not
// proto.__proto__ i.e. Object.getPrototypeOf(proto)
// since in other case we can go away to i-looping
let parentProto = proto.__parentProto__;
// remove "__parentProto__" property from
// our prototype to avoid i-looping; at 3rd and
// later levels the property will be found just deeper
delete proto.__parentProto__;
// call the parent method
parentProto[method].apply(this, [].slice.call(arguments, 1));
// and restore "__parentProto__" back
Object.defineProperty(proto, "__parentProto__", {
value: parentProto,
writable: true,
configurable: true
});
} });
Two more comments:
-
The code does not work if an instance method makes a super-call.
-
No matter where you are in a prototype chain, you will always delete and restore parentProto in the second chain member (in the prototype of |this|). Have you tested it with more than two recursive super-calls?
What might work is something like the following:
When you look for super.foo(), traverse the prototype chain, starting at |this|. Look for the second occurrence of "foo". Record the object where you have found it in this["obj_of_super_"+"foo"] (which you delete after the super-call returns). In subsequent super-calls, you can start your search at this.obj_of_super_foo (instead of |this|).
The above effectively records where a method has been found, but does it more indirectly (and waits until super has been called).
However, this still fails when one of the super.foo() starts to call this.foo() (which is perfectly OK if there are parameters involved that prevent an infinite recursion).
but another alternative is for super() within a direct call() or apply()
to be an error.
For me personally that's not an acceptable limitation to place on super compared to the limitation of having super lexically bound to a declaration.
Sorry, Jeremy, my comments were directed at Dmitry’s approach, not yours!
However, the call() problem is probably universally tricky, as you say.
Another point to consider: What happens if there are several calls super.foo() and the last one makes a call this.foo() (infinite recursion can be prevented via a parameter)
On Tue, Nov 1, 2011 at 2:58 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another point to consider: What happens if there are several calls super.foo() and the last one makes a call this.foo() (infinite recursion can be prevented via a parameter)
We're getting mixed up in time, as I just answered that in the previous message. To quote myself:
... and even for fancier cases where you restart the same super() chain
recursively
from the outside. Imagine if one of the deeper super.one()'s itself calls
obj.one(),
with a direct reference to the instance (and a different parameter, to
avoid infinite
recursion). You can imagine super counters as a stack instead of a value,
and
push a fresh one on, popping it when the new obj.one() call exits.
... a simple stack of records, one for each re-starting of the super() chain, should do. When calling super() from a function, it's easy for the runtime to tell if that function was itself invoked via super(), or from the top level.
My bad. I thought obj.foo() and this.foo() were different cases, but they obviously aren’t.
Invocation: B.describe.call(obj) should return "BA". With your library I would expect it to return "BBA".
Yes, it would. That's a good tricky case. I'm not sure if extra bookkeeping could make sense of that call, as there isn't any information as to where the function fits in to obj's prototype chain -- or if it's part of another prototype chain entirely. I'd like to hope that something could be figured out, but another alternative is for super() within a direct call() or apply() to be an error.
Another problem: What if an instance method makes a super-call?
A slightly less elegant (and performant) variant of your solution that works in both of the above cases is:
-
Keep track (per object and method) of the object where the previous super-call (e.g. super.foo()) ended up.
-
Start your search either there or at |this|: look for the second occurrence of property "foo". [optional: optimize non-|this| case]
On Tue, Nov 1, 2011 at 3:21 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another problem: What if an instance method makes a super-call?
A slightly less elegant (and performant) variant of your solution that works in both of the above cases is:
Keep track (per object and method) of the object where the previous super-call (e.g. super.foo()) ended up.
Start your search either there or at |this|: look for the second occurrence of property "foo". [optional: optimize non-|this| case]
Nope -- I think that design isn't the correct semantics. super() exists to delegate back to parent implementations of the current method. When calling a different instance method the calls should always start back out at the bottom of the inheritance hierarchy -- not at whatever level you happened to be on at the time.
If you haven't overridden the other instance method, then everything will work as expected ... and if you have overridden it, you need that overridden implementation to be called, otherwise you're breaking all kinds of expectations about the behavior of your code.
On 01.11.2011 22:21, Axel Rauschmayer wrote:
Can you show an example (and also the same example which is solved by es-proposed super-calls)?
let A = { describe() { return "A"; } } let B = A <| { describe() { return "B"+super.describe(); } } let C = B <| { describe() { return "C"+super.describe(); } } let obj = C <| {};
Invocation: B.describe.call(obj) should return "BA". With your library I would expect it to return "BBA".
Yes, obviously (and unfortunately). Thanks, it's a good catch.
Though, that's said, this lib was initially designed specially for class-system (and works in most of cases well); not for class-free super-calls. Yes, there are some subtle cases which aren't solve.
Furthermore, I don’t think your approach would work here:
let A = { one() { return this.two(); } two() { return "a"; } } let B = A <| { one() { return "B"+super.one(); } two() { return "b"+super.two(); } } let C = B <| { one() { return "C"+super.one(); } one() { return "b"+super.two(); } } let obj = C <| {};
Would obj.one() work? As far as I can tell, your bookkeeping works for one super recursion only, not for two.
Well, it works at least in respect of that it doesn't go to i-loop and doesn't break; the result is though "CBca", which seems isn't what you expect. Thanks for this example as well.
Dmitry.
On Nov 1, 2011, at 11:04 AM, Jeremy Ashkenas wrote:
I'm afraid that this super() discussion has gotten way off topic from the actual minimalist classes thread (which would be much more fun to talk about)
I agree and it's probably why I may sound slightly grumpy. Dealing with super is not a problem that we need to open up again. We have a sound proposal that has already been throughly discussed. This thread seems to be turning into a tutorial on the semantics of "super" in OO languages. I'd be happy to engage in that in another context but it is something that we really don't need to do here.
-- but in the interest of dynamic class bodies, let's run it down.
On Tue, Nov 1, 2011 at 12:45 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Jeremy, have you looked at the current super proposal at harmony:object_initialiser_super
I hadn't looked at the current super proposal in great depth, but thanks for the link.
It does not couple super use with classes at all. It does make it easy to define a super referencing method in an object or class literal, but the mechanism of super can be applied to any method defined in any object.
You are creating confusion by suggesting that the proposed ES6 super support "fails to work at all with regular prototypes".
Browsing through the examples on the page, the current super proposal does seem very coupled to "classes" ... by in this case being coupled to your proposed <| operator.
<| is also not coupled to "classes". <| is probably best thought of as a restricted form of the non-standard proto property.
If you try to use the proposed super with current standard prototype patterns, this happens:
Fox.prototype.dig = function() { super.dig(); };
As described in harmony:object_initialiser_super#imperatively_binding_super the proper way to do this is:
Object.defineMethod(Fox.prototype, "dig", function() { super.dig(); });
Using the "mustache" operator it could be more concisely expressed as:
Fox.prototype.{dig () { super.dig(); };
Use of these forms is necessary for the "plan b" static binding of super.
We possibly could redefine = to do the necessary rebinding. It is something that I have considered. However, it would introduces a number of issue. To start it makes every property assignment more expensive because a check must be made to see if the RHS evaluates to a function that may require resupering. It also would mean that sometimes property assignment does not preserve identity of the RHS value. Also, there is no way to disguising the cases were a property (perhaps array elements) is just being use for data storage of a function (perhaps a bound function with a super reference) and the cases where a property is actually being used as a method and need to be resupered. A number of these concerns also have backwards compat. issues with existing ES code.
(new Fox).dig(); TypeError: Object #<Object> has no method 'dig'
Likewise, for the other usual way of setting a prototype:
Fox.prototype = { dig: function() { super.dig(); } };
This is already problematic because it yields a malformed prototype object whose "constructor" property does not reference Fox.
As shown above .{ would be the property way to express this. Constructs of the form you use here probably should be linted.
(new Fox).dig(); TypeError: Object #<Object> has no method 'dig'
In the first case, the internal [[Super]] property is null. In the second case, the internal [[Super]] property is Object.prototype. Both cases will be quite frustrating for most JavaScript programmers.
Naturally, use of the <| operator instead of ".prototype" fixes this, but that coupling of the two syntaxes was the point I was trying to make. There is also Object.defineMethod() ... which would work for setting functions-that-use-super on a prototype imperatively, but that's far too verbose of a solution to propose for the common case. All that I'm trying to say is that the super() that JS.next adopts would do well to continue to work properly in the two examples above.
and that is one of the roles for .{
I think what we are seeing here are many of the reasons why designing ES.next is not nearly as simple and straightforward as some think it should be. We have a currently primarily exposes a number of composible low level primitives for constructing objects. User desire and we desire to provide higher level constructs for defining abstractions over objects. Those constructs need to be defined in terms of coordinated usage of the existing (and a few new) object construction primitive. However, the objects that are created still have to behave well when used in combination with primitively constructed objects that don't necessarily maintain the invariants assumed by the new higher-level constructs. In addition, we can't break any existing code and we want to make sure that usage of the new features can reasonably be added to existing code.
We don't have a clean slate and viable solultion have to make compromises from the ideal.
Just saying "I don't think that an efficient, pay-as-you-go dynamic super() will be easy, but with the technical chops of TC39 at your disposal, it should be possible" is not helpful, particular when you make other statement that suggest that you really don't understand all the semantic subtitles of super. I hate to cite "authority" but the semantics and implementation alternatives of super has been thoroughly explored over 30+ years by object-oriented language designer and implementer including myself. This is not a new problem, the "technical chops" have already been supplied. A unique new solution that no one has ever though of before would be welcomed. If you have such a solution, please put it forward. But don't assume that it is something that has not already been deeperly consider both by TC39 member and other language designers.
I hear you loud and clear, and would only be too happy to get off your lawn ;) All that I can cite is an utter lack of authority, and little experience with even undergraduate computer science -- there are very few assumptions here on my part. That said, if ES-Discuss wants to be an open forum, sometimes the rabble gets to speak up and be ignorant.
I really welcome your participation, and would particular like to get back to talking about "classes". You certainly belong here. Generally I don't mind "tutoring the rabble" (your term, not mine) but in this particularly case it is a distraction.
So, speaking from ignorance, here's a simple proof of concept in ES3 of how dynamic super calls can work with a little bit of metadata:
... which prints out:
In the GreatGrandParent. In the GrandParent. In the Parent. In the Child.
In a more ideal implementation, you'd use references to protos instead of counters, and your metadata would be scoped to be specific to an [object, methodName] pair ... but I think that this example is a bit clearer.
Hopefully this solution isn't unique or new, but dynamic languages are powerful things, and I would hope that dynamic super() is a pattern that's not logically impossible or inherently inefficient to implement, given a bit of extra bookkeeping behind the scenes.
To quick comments:
- the problem with super start to occur when you have downward this calls (calls from a super accessed method that calls to via this to methods closer to the leaf of the prototype hierarchy). This is where unintended loops can occur if you aren't aren't careful in your design. I don't see your example exercise this use case.
- maintaining state around the inner call is going to have the same sort of problem I pointed out about Demitry's proposal.
On Nov 1, 2011, at 12:27 PM, Jeremy Ashkenas wrote:
On Tue, Nov 1, 2011 at 3:21 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another problem: What if an instance method makes a super-call?
A slightly less elegant (and performant) variant of your solution that works in both of the above cases is:
Keep track (per object and method) of the object where the previous super-call (e.g. super.foo()) ended up.
Start your search either there or at |this|: look for the second occurrence of property "foo". [optional: optimize non-|this| case]
Nope -- I think that design isn't the correct semantics. super() exists to delegate back to parent implementations of the current method. When calling a different instance method the calls should always start back out at the bottom of the inheritance hierarchy -- not at whatever level you happened to be on at the time.
Who says? See allenwb/ESnext-experiments/blob/master/ST80collections-exp1.js#L419
Note that this is a direct transliteration of code from one of the first and longest lived object-oriented class libraries (see comments at the head of the file). So super calls to "other methods" have a long history. It is a relatively rare technique, but sometimes it is essential. In this particular case, a super call to the same method would produce a loop. In this case choice of doing this.do and super.do needed to be made by the original programmer based upon their decomposition of methods.
On Nov 1, 2011, at 12:46 PM, Allen Wirfs-Brock wrote:
On Nov 1, 2011, at 12:27 PM, Jeremy Ashkenas wrote:
On Tue, Nov 1, 2011 at 3:21 PM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another problem: What if an instance method makes a super-call?
A slightly less elegant (and performant) variant of your solution that works in both of the above cases is:
Keep track (per object and method) of the object where the previous super-call (e.g. super.foo()) ended up.
Start your search either there or at |this|: look for the second occurrence of property "foo". [optional: optimize non-|this| case]
Nope -- I think that design isn't the correct semantics. super() exists to delegate back to parent implementations of the current method. When calling a different instance method the calls should always start back out at the bottom of the inheritance hierarchy -- not at whatever level you happened to be on at the time.
Who says? See allenwb/ESnext-experiments/blob/master/ST80collections-exp1.js#L419
Ruby vs. Smalltalk -- FIGHT! :-P
Note that this is a direct transliteration of code from one of the first and longest lived object-oriented class libraries (see comments at the head of the file). So super calls to "other methods" have a long history. It is a relatively rare technique, but sometimes it is essential. In this particular case, a super call to the same method would produce a loop. In this case choice of doing this.do and super.do needed to be made by the original programmer based upon their decomposition of methods.
I agree, it's not necessary to restrict the super special form to super() instead of super.foo() or super.bar(). It may be good practice most of the time, but it's not dogma.
On Nov 1, 2011, at 11:06 AM, Allen Wirfs-Brock wrote:
On Nov 1, 2011, at 10:57 AM, Brendan Eich wrote:
On Nov 1, 2011, at 10:47 AM, Dmitry Soshnikov wrote:
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
We're not going to delete and restore. That's a non-starter for performance and observable mutation reasons.
the mutation is so wrong, and not just for performance reasons.
So wrong, don't get me started.
It's important to avoid non-starters that delete, but more than that, we have a pigeon-hole problem. You can't solve it using the heap and nested save-and-restore mutation. That's observable and it doesn't compose.
Back to minimal classes with super factored out nicely as Allen has done. I have a counter-proposal brewing.
On 01.11.2011 22:53, Axel Rauschmayer wrote:
Can you show an example (and also the same example which is solved by es-proposed super-calls)?
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well.
Code taken from gist.github.com/1330574#L68
Object.defineProperty(Object.prototype, "super", { value: function (method) { let proto = Object.getPrototypeOf(this);
// we should use exactly this link and not // proto.__proto__ i.e. Object.getPrototypeOf(proto) // since in other case we can go away to i-looping let parentProto = proto.__parentProto__; // remove "__parentProto__" property from // our prototype to avoid i-looping; at 3rd and // later levels the property will be found just deeper delete proto.__parentProto__; // call the parent method parentProto[method].apply(this, [].slice.call(arguments, 1)); // and restore "__parentProto__" back Object.defineProperty(proto, "__parentProto__", { value: parentProto, writable: true, configurable: true });
} });
Two more comments:
- The code does not work if an instance method makes a super-call.
Yep, and the reason is still the same -- the lib initially was designed to work in border of classes and covers most of cases (excluding subtle). Perhaps it can be adjusted to support super calls from instance method.
Anyway, that's said, I showed it only to refer the issue which basically isn't a (big) issue and can be solved even with a library -- of course in borders of some aspect -- in particular -- with using classes, not class-free super-calls with subtle cases.
Though, the thread is moved to discussion of class-free super-calls, meanwhile it's still good to understand which easy (and syntactically easy!) solution we may have for classes in ES.
- No matter where you are in a prototype chain, you will always delete and restore parentProto in the second chain member (in the prototype of |this|). Have you tested it with more than two recursive super-calls?
Nope, I didn't tested; you may send me an example though, it will be helpful.
What might work is something like the following:
When you look for super.foo(), traverse the prototype chain, starting at |this|. Look for the second occurrence of "foo". Record the object where you have found it in this["obj_of_super_"+"foo"] (which you delete after the super-call returns). In subsequent super-calls, you can start your search at this.obj_of_super_foo (instead of |this|).
The above effectively records where a method has been found, but does it more indirectly (and waits until super has been called).
Yes, it's also can be a solution. My solution is just "one-of". As you
saw, I used other solutions for this -- mostly with using caller
and
statically saving the method name (which is OK for SpiderMonkey which
has name
property for functions).
However, this still fails when one of the super.foo() starts to call this.foo() (which is perfectly OK if there are parameters involved that prevent an infinite recursion).
It's also a tricky case. I would even say -- "an error in programming logic". In static language I guess you expect that this.foo() then calls foo of the super (current) class but not goes again from the instance, right? Don't know, it's really a subtle case, I didn't encounter it in practical programming, though I agree that the case should be considered when designing a language.
Dmitry.
On 01.11.2011 23:55, Brendan Eich wrote:
On Nov 1, 2011, at 11:06 AM, Allen Wirfs-Brock wrote:
On Nov 1, 2011, at 10:57 AM, Brendan Eich wrote:
On Nov 1, 2011, at 10:47 AM, Dmitry Soshnikov wrote:
The technique I showed of course initially is designed to be used with class-system; though, I think it can be adopted to class-free system as well. We're not going to delete and restore. That's a non-starter for performance and observable mutation reasons. the mutation is so wrong, and not just for performance reasons. So wrong, don't get me started.
It's important to avoid non-starters that delete, but more than that, we have a pigeon-hole problem. You can't solve it using the heap and nested save-and-restore mutation. That's observable and it doesn't compose.
Don't be worry ;) Once again, it wasn't proposed as "let's standardize it". It was just an example to show that the problem isn't so hard as described (well, for a library). It's just a library, but the working library. ... in most simple cases.
Dmitry.
For the record: I do think Allen’s design is superior to what I’ve seen so far and it also most clearly reflects the semantics of |super| (apart from a non-starter dynamic solution). Sorry for the off-topic contributions, I liked the puzzle of finding an alternative.
Nope -- I think that design isn't the correct semantics. super() exists to delegate back to parent implementations of the current method. When calling a different instance method the calls should always start back out at the bottom of the inheritance hierarchy -- not at whatever level you happened to be on at the time.
If you haven't overridden the other instance method, then everything will work as expected ... and if you have overridden it, you need that overridden implementation to be called, otherwise you're breaking all kinds of expectations about the behavior of your code.
FWIW: here is the code. gist.github.com/1331748
+1
to me, this is far more intuitive than the current proposal.
On Mon, Oct 31, 2011 at 9:57 PM, Jeremy Ashkenas <jashkenas at gmail.com>wrote:
'Evening, ES-Discuss.
After poking a stick in the bees' nest this morning (apologies, Allen), and in the spirit of loyal opposition, it's only fair that I throw my hat in the ring.
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
Let me know what you think, and feel free to fork.
Thanks for this proposal, it brought together a few ideas of my own for a minimal class proposal enough that I'd like to take what you had and run with it, including some other things I've been trying to get working together for some time now.
I'm sure that I'm missing some very important things, but this is what I would LOVE to see in ES.next for classes, <|, super, and traits
On Oct 31, 2011, at 6:57 PM, Jeremy Ashkenas wrote:
'Evening, ES-Discuss.
After poking a stick in the bees' nest this morning (apologies, Allen), and in the spirit of loyal opposition, it's only fair that I throw my hat in the ring.
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
Let me know what you think, and feel free to fork.
Thanks, I did fork, and I made a Rich Corinthian Leather version, for the reasons given in the comments. In brief, I contend that over-minimizing will please no one: class haters still gonna hate, while class lovers used to batteries-and-leather-included will find the bare sheet metal poky and painful.
Love it or hate it, I'm ok either way :-P. But I do crave intelligent responses.
On Nov 1, 2011, at 5:18 PM, Brendan Eich wrote:
On Oct 31, 2011, at 6:57 PM, Jeremy Ashkenas wrote:
'Evening, ES-Discuss.
After poking a stick in the bees' nest this morning (apologies, Allen), and in the spirit of loyal opposition, it's only fair that I throw my hat in the ring.
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
Let me know what you think, and feel free to fork.
Thanks, I did fork, and I made a Rich Corinthian Leather version, for the reasons given in the comments. In brief, I contend that over-minimizing will please no one: class haters still gonna hate, while class lovers used to batteries-and-leather-included will find the bare sheet metal poky and painful.
Love it or hate it, I'm ok either way :-P. But I do crave intelligent responses.
Handy link included: gist.github.com/1332193
On 01/11/11 22:18, Brendan Eich wrote:
On Oct 31, 2011, at 6:57 PM, Jeremy Ashkenas wrote:
'Evening, ES-Discuss.
After poking a stick in the bees' nest this morning (apologies, Allen), and in the spirit of loyal opposition, it's only fair that I throw my hat in the ring.
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
Let me know what you think, and feel free to fork.
Thanks, I did fork, and I made a Rich Corinthian Leather version, for the reasons given in the comments. In brief, I contend that over-minimizing will please no one: class haters still gonna hate, while class lovers used to batteries-and-leather-included will find the bare sheet metal poky and painful.
Love it or hate it, I'm ok either way :-P. But I do crave intelligent responses.
/be
I like how clean the syntax is there, Brendan. I still feel class syntax would have more value if they presented a nice way for object composition besides inheritance.
None the less, I think we could well get rid of those `var' inside the class-body, as long as it's not executable:
On Nov 1, 2011, at 6:57 PM, Quildreen Motta wrote:
I like how clean the syntax is there, Brendan. I still feel class syntax would have more value if they presented a nice way for object composition besides inheritance.
Traits were factored out and I consider adding them in this exercise to be a "bridge too far".
None the less, I think we could well get rid of those `var' inside the class-body, as long as it's not executable:
I don't think so, although Bob Nystrom's suggestions ended up simplified that way. It's awkward to use assignment expression as a declarative form. It suggests the body is executable. Instead, I went with 'var' as Bob did originally.
My view is that 'var' will die hard and for global-object-aliasing global variables, it's close to class prototype properties. There's no way to observe the prototype half-initialized. As noted, there's a risk people will think methods close over proto-vars/consts.
On 02/11/11 00:01, Brendan Eich wrote:
On Nov 1, 2011, at 6:57 PM, Quildreen Motta wrote:
I like how clean the syntax is there, Brendan. I still feel class syntax would have more value if they presented a nice way for object composition besides inheritance. Traits were factored out and I consider adding them in this exercise to be a "bridge too far".
Fair enough.
None the less, I think we could well get rid of those `var' inside the class-body, as long as it's not executable: I don't think so, although Bob Nystrom's suggestions ended up simplified that way. It's awkward to use assignment expression as a declarative form. It suggests the body is executable. Instead, I went with 'var' as Bob did originally.
My view is that 'var' will die hard and for global-object-aliasing global variables, it's close to class prototype properties. There's no way to observe the prototype half-initialized. As noted, there's a risk people will think methods close over proto-vars/consts.
/be
Hm, I don't think lhs = rhs' suggests any more of a non-declarative form than
var'. Taking JS semantics into consideration, I'd say var' is more misleading (whereas
lhs = rhs' just feels weird).
There's `public', but then, that's quite verbose. I guess people comming from a classical language with declarative definitions for class slots might still be used to it, none the less.
On Nov 1, 2011, at 7:17 PM, Quildreen Motta wrote:
There's `public', but then, that's quite verbose. I guess people comming from a classical language with declarative definitions for class slots might still be used to it, none the less.
I thought of that, but then 'public const'? Muy Verboso!
On Tue, Nov 1, 2011 at 19:32, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 1, 2011, at 7:17 PM, Quildreen Motta wrote:
There's `public', but then, that's quite verbose. I guess people comming from a classical language with declarative definitions for class slots might still be used to it, none the less.
I thought of that, but then 'public const'? Muy Verboso!
I also prefer var since it fits better with const.
class C { var x = 1; const y = 2; ... }
is better than
class C { x = 1; const y = 2; ... }
Another problem with skipping var (which we saw in Traceur) was that without both var and initializer it looks really strange.
class D { z; ... }
I really hope that “let is the new var” will hold and won’t be replaced with “let is the new var, except for class declarations and global variables”.
One more possibility (in addition to not writing anything at all and using let instead of var): class C { public x = 0, y = 0; // the analog to private const foo = "abc"; }
Two more thoughts:
- We are becoming more verbose than object literals (with either var or public). I had hoped that we could preserve their lightweight spirit.
- How often will one define non-function prototype properties? It could even be considered an anti-pattern. That might make it easier to decide on verbose syntax.
[Oops. Replied to Brendan personally rather than reply-all to the list.]
On Nov 1, 2011, at 10:27 PM, David Flanagan wrote:
[Oops. Replied to Brendan personally rather than reply-all to the list.]
My reply was private too. Here it is:
On Nov 1, 2011, at 10:15 PM, David Flanagan wrote:
On 11/1/11 5:18 PM, Brendan Eich wrote:
Love it or hate it, I'm ok either way :-P. But I do crave intelligent responses.
I think reusing var and const in this context will be considered a wart on the language. If I've got to prefix an identifier with 'this' then it is a property, not a variable or a constant. We all know that global variables are properties of the global object, but we don't think of them that way. They're still just global variables. So I don't think that "cat's out of the bag" argument for using them here flies.
Yeah, sure -- global object is not on scope chain in Harmony anyway. My point was less technical than metaphorical -- and note how Dart uses var (C# too).
I've got four alternatives to suggest:
- Class bodies are new syntax, so you can introduce new keywords, right? So 'prop' or 'proto'? 'static' puts a property on the class. 'private' puts a (private) property on the instance. So 'proto' could put a property on the prototype.
Yes, but prefix keywords in front of every member? Too heavy. Labeled sections are problematic grammatically and most on TC39 hate 'em. prefixed braced sub-bodies over-indent.
There ought to be a sane unprefixed default. Then, though, we still have two choices:
(a) Data property initialiser syntax (x: v with comma separator).
(b) Assignment expression form (x = v with semicolon separator or terminator and some kind of ASI consistency).
Neither is great. I lean toward (b). You?
- As others have suggested, use 'public' and 'public const' instead of 'var' and 'const'. I'd prefer that 'public' be like 'private' and define an instance variable (see below), though, so I don't really like this alternative.
Agreed. Also, you can't initialize instance variables at the class body element level.
- Most classical OO languages just have instance properties and class properties. This distinction between instance properties and prototype properties is a JavaScript thing.
Kind of -- although are methods "instance properties" in other OO languages? Not quite. They're in a shared singleton-per-class vtable or the like.
And if we're trying to emulate classical OO, then how about just dropping the ability to define data properties on the prototype.
I am all for that! I kept it for parity with jashkenas's three.js example, but the r, g, and b props there are vacuous default proto-props (ditto the Monster example's proto var and const).
- Do your class bodies allow getters and setters?
Sure, they're easy. Left 'em out for parity and brevity. They follow the property initialiser in object literal pattern.
Your private instance variables are quite interesting.
Yes, that is a missing ingredient in most proposals we've seen.
One nit: you're using @ as both a sigil in @name and as an operator in other at name. I would expect other. at name instead.
No, it's a prefix operator if in operand context (like / starting a regexp), a binary operator in operator context (/ as division).
Trying to avoid .@ all over, and this.@ noise.
And a question: do we really need both the private declaration and the sigil?
Yes. Otherwise the private declaration takes over all dot references of any such name, and that's wrong. We have no static types to consult, so we have to use a different sigil/operator.
Note @ in Ruby for instance-private ivars. You have to message an "other" parameter to get its x and y (if it's a point) or have that message send fail at runtime -- or check its type in your dyadic method and double-dispatch, etc.
But more to the point, you've defined a syntax that allows us to drop 'this' from our methods! That's seriously cool.
Only for private vars.
So cool that I expect using private properties will become the default and normal public properties will be the exception. Unless... Can you also allow 'this' to be dropped for public instance variables? With a 'public' declaration and/or a different sigil?
We could use the same sigil/namespace, but then public and private names could collide and that seems future hostile. I may have API growth where a common name I've used for a private is now required for a public in order to interface to some third party code.
What about just a dot with nothing on the left hand side? Can '.foo' be shorthand for 'this.foo'? Or are there grammatical issues with that?
That could work with [no LineTerminator here] restriction on the left, which is awkward -- it mandates manual semicolon insertion of you assign .foo = bar after a baz() statement. We thought about this in consider how to reform with, but that's a dead end.
Another sigil is hard to justify for the this.publicProp case, and for dyadic methods, etc., other.publicProp is how you spell it -- the dot, I mean. So one would want this.publicProp anyway. Between this consideration and the preference for private, I'm fine stopping with @ for private.
On Nov 1, 2011, at 8:58 PM, Axel Rauschmayer wrote:
I really hope that “let is the new var” will hold and won’t be replaced with “let is the new var, except for class declarations and global variables”.
Globals may be an issue, we see patterns like this
var foo; if (typeof foo == "undefined") foo = function () {...};
The var is required by ES5 strict here.
But never mind var in class, I've updated gist.github.com/1332193 to go with the data property initialiser flow, but keep the batteries-included/rich-cordoba-leather appeal of const/private/static optional prefixes.
This avoids the funky looking unprefixed assignment or standalone name issue Arv just mentioned too.
Le 02/11/2011 07:53, Brendan Eich a écrit :
On Nov 1, 2011, at 10:27 PM, David Flanagan wrote:
- Do your class bodies allow getters and setters?
Sure, they're easy. Left 'em out for parity and brevity. They follow the property initialiser in object literal pattern.
They would be great especially in combinaison with the private syntax
Your private instance variables are quite interesting.
Yes, that is a missing ingredient in most proposals we've seen.
Indeed. I was already seeing myself writing:
{ let name = Name.create(), health = Name.create(); // ... class bla{ method1(a){ this[health] = a; } } }
which would have been annoying as hell. If there is a class syntax, it needs proper instance private properties syntax.
I'm not a huge fan of prefixed name for private instances, but after deeper thought, its seems unavoidable. I prefer @ to Dart's _ for the only reason that @ is currently forbidden in JavaScript identifiers, preventing all confusion.
But more to the point, you've defined a syntax that allows us to drop 'this' from our methods! That's seriously cool.
Only for private vars.
Within a class method definition, if an identifier is undeclared, couldn't it be bound to the public instance property the method is called on? ... wait a minute. That's a terrible idea! Let's keep it to only private properties and with a prefix!
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea. Is "other" a monster? (how do you "recognize" a monster from any other object?). If so, is it a good enough reason for you to be able to access its private state? If other is not a monster, what is the behavior?
What happens in the following case?
class Monster{ private health;
constructor(health) { @health = health; } }
let m = new Monster(100);
// pass m to a potentially malicious script:
m.kill = function(){ @health = 0; } m.kill();
Or, how do you prevent someone who has access to the object to define a function which accesses the private state? Is the @-syntax only allowed within a class body?
Here's a simple super suggestion:
super Foo.bar(x) should desugar to Foo.prototype.bar.call(this, x)
If people make constructors of the type that are designed to be called with new then it works for constructors too.
// Works with super, used with var foo = new Foo.prototype.constructor() constructor: function() { this.x = 0; this.y = 0; }
// Doesn't work with super, used with var foo = Foo.prototype.constructor() constructor: function() { return {x: 0, y: 0} }
// Also doesn't work with super constructor: function() { var self = factory(); self.x = 0; self.y = 0; return self; }
The new more object-literal-style syntax is great. "private" sticks out a bit, but it’s not a deal-breaker. It could even be good for the code if it had to appear at the beginning (kind of like a section). Such a section would be great for object literals, too. Maybe we can find a syntax that works for both (as in future-friendly)!
And if we're trying to emulate classical OO, then how about just dropping the ability to define data properties on the prototype.
I am all for that! I kept it for parity with jashkenas's three.js example, but the r, g, and b props there are vacuous default proto-props (ditto the Monster example's proto var and const).
Great great idea! Very newbie-friendly. The error message could tell you: put those properties into the constructor.
Your private instance variables are quite interesting.
Yes, that is a missing ingredient in most proposals we've seen.
+1
One nit: you're using @ as both a sigil in @name and as an operator in other at name. I would expect other. at name instead.
No, it's a prefix operator if in operand context (like / starting a regexp), a binary operator in operator context (/ as division).
Trying to avoid .@ all over, and this.@ noise.
I think people have certain expectations for how property access looks (and JavaScript is a better language for it; I don’t like how Java instance properties become part of the local scope). My guess: The expectation would be either one of two notations.
this at name, other at name
this. at name, other. at name
What about just a dot with nothing on the left hand side? Can '.foo' be shorthand for 'this.foo'? Or are there grammatical issues with that?
That could work with [no LineTerminator here] restriction on the left, which is awkward -- it mandates manual semicolon insertion of you assign .foo = bar after a baz() statement. We thought about this in consider how to reform with, but that's a dead end.
Another sigil is hard to justify for the this.publicProp case, and for dyadic methods, etc., other.publicProp is how you spell it -- the dot, I mean. So one would want this.publicProp anyway. Between this consideration and the preference for private, I'm fine stopping with @ for private.
I think the namespacing via this is a feature, it give the code a nice uniform look: if (this.foo === other.foo)
Axel
super Foo.bar(x) should desugar to Foo.prototype.bar.call(this, x)
harmony:object_initialiser_super
What is that you don’t like about Allen’s proposal? You are still hard-coding the name of the super-constructor (which is what super
nicely avoids).
If people make constructors of the type that are designed to be called with new then it works for constructors too.
// Works with super, used with var foo = new Foo.prototype.constructor() constructor: function() { this.x = 0; this.y = 0; }
// Doesn't work with super, used with var foo = Foo.prototype.constructor() constructor: function() { return {x: 0, y: 0} }
// Also doesn't work with super constructor: function() { var self = factory(); self.x = 0; self.y = 0; return self; }
I don’t understand. Are you really pointing out something that will cause difficulty with your proposal or a general problem?
2011/11/2 Axel Rauschmayer <axel at rauschma.de>:
super Foo.bar(x) should desugar to Foo.prototype.bar.call(this, x)
harmony:object_initialiser_super
What is that you don’t like about Allen’s proposal? You are still hard-coding the name of the super-constructor (which is what
super
nicely avoids).
"lookup starts with the object that is the prototype of the object defined by the object literal that contains the reference to super."
So it only works inside an object literal, whereas mine is easier to understand and works anywhere. If you are using it without any of the other stuff you can even trivially get your minifier to desugar for older browsers.
I don't think hard coding the name of the super-constructor is a problem. On the contrary it is documentation of something that is implicit in the normal use of super, which I regard as a problem. It's not like the programmer doesn't know what the superclass is at the moment when he writes it, so it's nice if he documents that knowledge right there where the reader needs it.
I don't think you can refactor a prototype hierarchy without taking a good long look at every use of the super keyword, whether or not it requires explicit statement of the expected prototype.
If people make constructors of the type that are designed to be called with new then it works for constructors too.
// Works with super, used with var foo = new Foo.prototype.constructor() constructor: function() { this.x = 0; this.y = 0; }
// Doesn't work with super, used with var foo = Foo.prototype.constructor() constructor: function() { return {x: 0, y: 0} }
// Also doesn't work with super constructor: function() { var self = factory(); self.x = 0; self.y = 0; return self; }
I don’t understand. Are you really pointing out something that will cause difficulty with your proposal or a general problem?
It's a limitation of my proposal, but not a serious one IMHO. The proposal is simple enough that it hardly qualifies as a surprise.
I don't know how other super proposals deal with calling constructors that are not designed to work with the object that the new keyword brings into being and passes as 'this'.
On Tue, Nov 1, 2011 at 8:18 PM, Brendan Eich <brendan at mozilla.com> wrote:
Thanks, I did fork, and I made a Rich Corinthian Leather version, for the reasons given in the comments. In brief, I contend that over-minimizing will please no one: class haters still gonna hate, while class lovers used to batteries-and-leather-included will find the bare sheet metal poky and painful.
Love it or hate it, I'm ok either way :-P. But I do crave intelligent responses.
When it comes to classes ... one man's minimalist is another man's baroque ;)
Since you've updated the Rich Corinthian Leather edition to return to (extended) object literals as the RHS of the class definition -- we're now effectively in agreement about the class proposal: the two flavors are the same, if object literal extensions are added to JS.next, and the RHS is restricted to be a literal instead of an expression, as super() calls might necessitate. Groovy.
I think that if class bodies must be statically analyzable lists of properties, then you can't go wrong with object literals. They're already widely renowned syntax for such purposes -- so much so that languages like Ruby are re-syntaxing their hashes to follow suit.
That said, we should take care to have the two things be fully consistent: A class body should be an object literal, with no subtle differences in the grammar leading to stumbling blocks down the line. If class bodies can have:
class Runner { run() { ... } private quit() { ... } const speed: 10 }
... then an object literal should equally be able to have an identical body:
var runner = { run() { ... } private quit() { ... } const speed: 10 };
... as const properties and private methods on single objects are just as useful and important as const properties and private methods on objects that happen to be instances of a class.
Hopefully, unifying the two will have the side effect of making it easier for TC39 to come to consensus on classes, if folks can agree to the general "class name objectLiteral" scheme. You can do it (and desugar it) today with ES3 object literals, and if ES6 object literals happen to have public, const, and shorthand method syntax, ES6 classes will naturally have those features as well.
(Full Disclosure: I'm still very opposed to const, private, and their object-lockdown friends, but that should have no bearing on Brendan's proposal.)
On 02/11/11 11:01, Erik Corry wrote:
2011/11/2 Axel Rauschmayer<axel at rauschma.de>:
super Foo.bar(x) should desugar to Foo.prototype.bar.call(this, x) harmony:object_initialiser_super
What is that you don’t like about Allen’s proposal? You are still hard-coding the name of the super-constructor (which is what
super
nicely avoids). "lookup starts with the object that is the prototype of the object defined by the object literal that contains the reference to super."So it only works inside an object literal, whereas mine is easier to understand and works anywhere. If you are using it without any of the other stuff you can even trivially get your minifier to desugar for older browsers. I don't think hard coding the name of the super-constructor is a problem.
It is when you take into account that functions in JavaScript are not bound to an object, they are generic. You can simply assign any function to any object and it'll most likely just work.
So, what I mean is that, if you have `super' resolve to a hard-coded constructor name this is what you get (hell, I don't even use constructors anymore, those ugly, ugly things):
function Thing(name) { this.name = name } Thing.prototype.describe = function(){ console.log('A thing ' + this.name) }
function Subject(name) { super.constructor(name) } Subject.prototype = Object.create(Thing.prototype) Subject.prototype.constructor = Subject Subject.prototype.describe = function() { super.describe() console.log('A subject ' + this.name) }
// For now, all is well, `super' in Subject will always resolve to Thing. var car = new Subject('car') car.describe() // => 'A thing car' // => 'A subject car'
// But remember that you can take any function and assign to any object // Such that the following wouldn't work -- we want Teddy to be just // a Subject here, not a Thing. var teddy = { name: 'teddy', describe: Subject.describe } teddy.describe() // => 'A subject teddy' // => 'A thing teddy'.
// Whereas with Allen's proposal, everything would work okay: var teddy = { name: 'teddy' }
// The defineMethod call creates a new function `describe' that // has its |super| reference bound to teddy's prototype Object.defineMethod(teddy, 'describe', Subject.describe)
// And the new `describe' function can use this static |super| to // determine where lookup starts teddy.describe() // => 'A subject teddy'
Of course, applying a function to another object is a different matter, which won't be solved by static super.
Le 02/11/2011 14:26, Jeremy Ashkenas a écrit :
(Full Disclosure: I'm still very opposed to const, private, and their object-lockdown friends, ....)
Could you elaborate on this point? All object-lockdown I can think (non-configurability, non-writability, non-enumerability, private names, const variables, const classes) of is optional. Why are you against them?
Regarding "const", it's an optional keyword basically telling the interpreter "hey, the value isn't suppose to change at runtime, please ensure it!". It prevents bugs of mistakenly redefining something that shouldn't be redefined. Why are you opposed to this?
Regarding "private", I'm puzzled. Having private attributes in objects is necessary to implement encapsulation and get all the benefits of good object-oriented practices. A generation of JS programmers have used scope constructs and the "var" keyword to enable some privacy. I'm all in favor of providing a declarative support for what people have done for years anyway. What is wrong with "private"?
Once again, all of this is optional, nothing forces you to use new features of the language. I will personnally never use multi-line strings, but I don't mind the feature being in the language.
I was about to say "if you're unsatisfied, create a language which doesn't provide features you don't like, like Coffeescript", but... humm... So, why being against the introduction of features in the language?
I don’t think Allen’s proposal is controversial, any more. It’s how things work in almost all mainstream (OO) languages. I don’t see myself using super
anywhere outside an object literal – except for a few cases where Object.defineMethod() works just fine. Do you have any real-world examples where you want to use super
outside an object literal? Caveat: In some contexts, you need the extension "monocle" operator to make object literals viable.
On 02/11/11 13:01, David Bruant wrote:
Le 02/11/2011 14:26, Jeremy Ashkenas a écrit :
(Full Disclosure: I'm still very opposed to const, private, and their object-lockdown friends, ....)
Regarding "const", it's an optional keyword basically telling the interpreter "hey, the value isn't suppose to change at runtime, please ensure it!". It prevents bugs of mistakenly redefining something that shouldn't be redefined. Why are you opposed to this?
Yeah, immutability and referential transparency are wonderful things. I
wish JavaScript developers would rely more on it, it makes things saner
to work with. Though is const' the same as Object.freeze(thing) when
thing' is an object? Or does it just ensure that the slot in the object
isn't reassigned?
Regarding "private", I'm puzzled. Having private attributes in objects is necessary to implement encapsulation and get all the benefits of good object-oriented practices.
I still think `private' is quite overrated. It gets terrible if you want to test something and the developer went through all the trouble of making proxies to access everything, as well as making everything stateful.
That said, there are some valid use-cases for it... I guess?
Does object at name break encapsulation? One could mutate object at name for any instance of the class passed in via a parameter for example.
Le 02/11/2011 16:15, Quildreen Motta a écrit :
On 02/11/11 13:01, David Bruant wrote:
Le 02/11/2011 14:26, Jeremy Ashkenas a écrit :
(Full Disclosure: I'm still very opposed to const, private, and their object-lockdown friends, ....)
Regarding "const", it's an optional keyword basically telling the interpreter "hey, the value isn't suppose to change at runtime, please ensure it!". It prevents bugs of mistakenly redefining something that shouldn't be redefined. Why are you opposed to this? Yeah, immutability and referential transparency are wonderful things. I wish JavaScript developers would rely more on it, it makes things saner to work with. Though is
const' the same as Object.freeze(thing) when
thing' is an object? Or does it just ensure that the slot in the object isn't reassigned?
It depends on the context. If const is used in front of a variable, the variable value will remains constant. The definition of a const class is given by Brendan (gist.github.com/1332193 ~l.239). There is a semantics of const functions I don't remember.
Regarding "private", I'm puzzled. Having private attributes in objects is necessary to implement encapsulation and get all the benefits of good object-oriented practices. I still think `private' is quite overrated. It gets terrible if you want to test something and the developer went through all the trouble of making proxies to access everything, as well as making everything stateful.
If you stab me, I won't accuse the knife. I agree with what you describe, but it's the problem of the developer misusing the feature rather than an inherent problem of the feature itself.
That said, there are some valid use-cases for it... I guess?
Encapsulation? Stability of interfaces? Better readability? The use case is more about code quality and expressiveness rather than adding a new capability to the language.
That said, there are some valid use-cases for it... I guess? Encapsulation? Stability of interfaces? Better readability? The use case is more about code quality and expressiveness rather than adding a new capability to the language.
The two most interesting use cases I see are (for all my other privacy needs I use naming conventions, but there are people who don’t like that):
- Avoid name clashes (e.g. when mixing in a trait, but also when doing subtyping).
- Enable special functionality. You could also use a naming convention here, but using a name object is nicer.
On Nov 2, 2011, at 4:10 AM, David Bruant wrote:
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea.
Private names do not leak via reflection, not even via proxies. So what's the problem?
Is "other" a monster? (how do you "recognize" a monster from any other object?).
You could do ad-hoc type or shape tests. For the example, and even in most cases in general, there's no need. Duck typing works with private names too.
If so, is it a good enough reason for you to be able to access its private state?
It must be an instance of this class or the name would not be bound.
If other is not a monster, what is the behavior?
You'd get undefined.
What happens in the following case?
class Monster{ private health;
constructor(health) { @health = health; } }
let m = new Monster(100);
// pass m to a potentially malicious script:
m.kill = function(){ @health = 0;
health is not in scope here!
On Nov 2, 2011, at 5:34 AM, Axel Rauschmayer wrote:
I think the namespacing via this is a feature, it give the code a nice uniform look: if (this.foo === other.foo)
You can use this at priv == other at priv if you like. My proposal, after Ruby and CoffeeScript, is to support @priv (with [no LineTerminator here] before the @) as shorthand for this at priv.
Try it out, you'll like it -- especially where there's no other at priv in sight.
On Nov 2, 2011, at 6:26 AM, Jeremy Ashkenas wrote:
That said, we should take care to have the two things be fully consistent: A class body should be an object literal, with no subtle differences in the grammar leading to stumbling blocks down the line. If class bodies can have:
class Runner { run() { ... } private quit() { ... }
const speed: 10 }... then an object literal should equally be able to have an identical body:
var runner = { run() { ... } private quit() { ... }
const speed: 10 };... as const properties and private methods on single objects are just as useful and important as const properties and private methods on objects that happen to be instances of a class.
I quite agree -- but I was focused on classes in that gist, not ready to take on object literals. I am sure Allen agrees, and probably most of TC39. We'll see.
(Full Disclosure: I'm still very opposed to const, private, and their object-lockdown friends, but that should have no bearing on Brendan's proposal.)
Sometimes you need to lock a door. Not having these facilities means you can't guarantee certain invariants. OTOH I bet you're worried everyone will do the stupid thing and lock down by default. That's not likely to survive on github or any JS library ecosystem where consumers have choice, so why worry?
As for private, if it's ok for Ruby to have @vars, it's ok for JS :-P.
On Nov 2, 2011, at 8:25 AM, Kam Kasravi wrote:
Does object at name break encapsulation? One could mutate object at name for any instance of the class passed in via a parameter for example.
At the Nov. 2008 TC39 meeting, we agreed on class-private instance variables, not instance-private ivars. There's no "break encapsulation" -- this is a difference in point of view. Classes are the locus or privacy, not instances. You cannot access a class-private ivar outside of the class's methods.
Le 02/11/2011 18:09, Brendan Eich a écrit :
On Nov 2, 2011, at 4:10 AM, David Bruant wrote:
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea. Private names do not leak via reflection, not even via proxies. So what's the problem?
My problem is that private names are per-class instead of per-instance. This is saying that an instance cannot isolate itself from other instances of the same class. It doesn't smell good to me in terms of encapsulation. I am a human being, you are a human being. It doesn't mean we can trust each other. If I have a password in my head, I don't want you to be able to access it just because we are both human beings. I want my password to be safe from foxes, cats, but also other human beings.
Is "other" a monster? (how do you "recognize" a monster from any other object?). You could do ad-hoc type or shape tests. For the example, and even in most cases in general, there's no need. Duck typing works with private names too.
Who is "you" in your case? My "you" was "runtime". I think runtime shouldn't do heuristic-tests like comparing shapes.
If so, is it a good enough reason for you to be able to access its private state? It must be an instance of this class or the name would not be bound.
Is it a good idea that all instances of the same class can access each others private state? I don't think this is possible in Java, I don't know an object-oriented language where this is possible. Is there a precedent of this? Doesn't it break encapsulation? I think that private names should be "per-instance per-property" rather than "per-class per-property".
"it must be an instance of this class". I'd like to ask the question again, how does runtime knows that an object is a instance of a given class? Will there be an internal property for this? [[Class]]? What is the value of this internal property for objects not created with classes?
What happens in the following case?
class Monster{ private health;
constructor(health) { @health = health; } }
let m = new Monster(100);
// pass m to a potentially malicious script:
m.kill = function(){ @health = 0; health is not in scope here!
Ok, cool :-)
2011/11/2 Quildreen Motta <quildreen at gmail.com>:
I don't think hard coding the name of the super-constructor is a problem.
It is when you take into account that functions in JavaScript are not bound to an object, they are generic. You can simply assign any function to any object and it'll most likely just work.
I think the chances are slim that you can take a function that does a super call, put it on a different object, and it will 'just work'. It's a pretty rare case.
<troll> C++ requires you to state the name of the super-class in super
calls, and Java doesn't. Do we want to be like Java? </troll>
On 11/1/11 11:53 PM, Brendan Eich wrote:
On Nov 1, 2011, at 10:27 PM, David Flanagan wrote:
- Class bodies are new syntax, so you can introduce new keywords, right? So 'prop' or 'proto'? 'static' puts a property on the class. 'private' puts a (private) property on the instance. So 'proto' could put a property on the prototype.
Yes, but prefix keywords in front of every member? Too heavy. Labeled sections are problematic grammatically and most on TC39 hate 'em. prefixed braced sub-bodies over-indent.
There ought to be a sane unprefixed default. Then, though, we still have two choices:
(a) Data property initialiser syntax (x: v with comma separator).
(b) Assignment expression form (x = v with semicolon separator or terminator and some kind of ASI consistency).
Neither is great. I lean toward (b). You?
I don't see why you can't use var syntax but with 'proto' as the keyword instead of 'var':
// All instances inherit these default values:
proto x = 0, y = 0;
Similarly for static members as well:
static const ORIGIN = new Point(0,0), ONE = new Point(1,0);
Of course, that takes you away from unification with object literals again... Though I don't think unifiying class bodies and object literals was one of your original goals.
Your private instance variables are quite interesting.
Yes, that is a missing ingredient in most proposals we've seen.
And inquiring minds want to know more about them. I assume they're
scoped to the entire class body, right? And they hoist to the top?
Could they have initializers that were automatically included in the
constructor?
A comment in your gist (lines 186-187) seems to say that with 'private x', we can use @x or this[x]. Is that really what you meant? Plain 'x' is bound to the Name object and @x is the value of the property with that name? Is x a variable? Does this mean that the class desugars into something that has a let statement surrounding the all the methods?
One nit: you're using @ as both a sigil in @name and as an operator in other at name. I would expect other. at name instead.
No, it's a prefix operator if in operand context (like / starting a regexp), a binary operator in operator context (/ as division).
Trying to avoid .@ all over, and this.@ noise.
I think ordinary unary @ would dominate and .@ would be relatively rare. But when used, it would at least look like noisy property access rather than an email address. My gut says that regularity of syntax wins over conciseness and noise here.
And a question: do we really need both the private declaration and the sigil?
Yes. Otherwise the private declaration takes over all dot references of any such name, and that's wrong. We have no static types to consult, so we have to use a different sigil/operator.
I'm not convinced that public and private properties ought to be in different namespaces, though I can see that that would have some advantages.
Note @ in Ruby for instance-private ivars. You have to message an "other" parameter to get its x and y (if it's a point) or have that message send fail at runtime -- or check its type in your dyadic method and double-dispatch, etc.
But more to the point, you've defined a syntax that allows us to drop 'this' from our methods! That's seriously cool.
Only for private vars.
So cool that I expect using private properties will become the default and normal public properties will be the exception. Unless... Can you also allow 'this' to be dropped for public instance variables? With a 'public' declaration and/or a different sigil?
We could use the same sigil/namespace, but then public and private names could collide and that seems future hostile. I may have API growth where a common name I've used for a private is now required for a public in order to interface to some third party code.
Because the private names are private, changing them would be easy to do, at least...
Do you expect the performance of private property lookup to be equivalent to public property lookup? That is will @foo take the same time as this.foo? Because everyone is going to want to use @foo for its convenience if there is not an equivalent trick for public properties.
What about just a dot with nothing on the left hand side? Can '.foo' be shorthand for 'this.foo'? Or are there grammatical issues with that?
That could work with [no LineTerminator here] restriction on the left, which is awkward -- it mandates manual semicolon insertion of you assign .foo = bar after a baz() statement. We thought about this in consider how to reform with, but that's a dead end.
You're right. Plain dot doesn't work.
Another sigil is hard to justify for the this.publicProp case, and for dyadic methods, etc., other.publicProp is how you spell it -- the dot, I mean. So one would want this.publicProp anyway. Between this consideration and the preference for private, I'm fine stopping with @ for private.
For what its worth, I'd be willing to live with another sigil for the convenience of not having to type 'this.' (But it still seems like it ought to be possible to unify both public and private declared properties with a single sigil, if you are willing to put them in the same namespace.)
On 02/11/11 15:42, Erik Corry wrote:
2011/11/2 Quildreen Motta<quildreen at gmail.com>:
I don't think hard coding the name of the super-constructor is a problem. It is when you take into account that functions in JavaScript are not bound to an object, they are generic. You can simply assign any function to any object and it'll most likely just work. I think the chances are slim that you can take a function that does a super call, put it on a different object, and it will 'just work'. It's a pretty rare case. "most likely". Also, it should work if |super| were dynamically resolved, as long as the object implemented what's required by the call.
On Nov 2, 2011, at 10:35 AM, David Bruant wrote:
Le 02/11/2011 18:09, Brendan Eich a écrit :
On Nov 2, 2011, at 4:10 AM, David Bruant wrote:
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea. Private names do not leak via reflection, not even via proxies. So what's the problem? My problem is that private names are per-class instead of per-instance.
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars.
You can make instance-private ivars yourself in the constructor using Name.create, and go to town. Knock yourself out! (I mean that in a good way, at least from where I sit :-P).
Is "other" a monster? (how do you "recognize" a monster from any other object?). You could do ad-hoc type or shape tests. For the example, and even in most cases in general, there's no need. Duck typing works with private names too. Who is "you" in your case? My "you" was "runtime". I think runtime shouldn't do heuristic-tests like comparing shapes.
We don't have types. I have no idea what you are proposing, but my point stands: there's no need to recognize another Monster and no capability leak. The code works as much JS today does with public property names.
On Nov 2, 2011, at 10:42 AM, Erik Corry wrote:
<troll> C++ requires you to state the name of the super-class in super calls, and Java doesn't. Do we want to be like Java? </troll>
Wimpy Troll, easily defeated. We want to be like Ruby and CoffeeScript!
On Nov 2, 2011, at 10:52 AM, David Flanagan wrote:
On 11/1/11 11:53 PM, Brendan Eich wrote:
On Nov 1, 2011, at 10:27 PM, David Flanagan wrote:
- Class bodies are new syntax, so you can introduce new keywords, right? So 'prop' or 'proto'? 'static' puts a property on the class. 'private' puts a (private) property on the instance. So 'proto' could put a property on the prototype.
Yes, but prefix keywords in front of every member? Too heavy. Labeled sections are problematic grammatically and most on TC39 hate 'em. prefixed braced sub-bodies over-indent.
There ought to be a sane unprefixed default. Then, though, we still have two choices:
(a) Data property initialiser syntax (x: v with comma separator).
(b) Assignment expression form (x = v with semicolon separator or terminator and some kind of ASI consistency).
Neither is great. I lean toward (b). You?
I don't see why you can't use var syntax but with 'proto' as the keyword instead of 'var':
I lean toward (a) now. I've made my peace with object initialiser syntax -- but with the Cordoba extensions for classes and object literals, as discussed with Jeremy today.
Your private instance variables are quite interesting.
Yes, that is a missing ingredient in most proposals we've seen.
And inquiring minds want to know more about them. I assume they're scoped to the entire class body, right?
Certainly.
And they hoist to the top?
That's how we roll where possible.
Could they have initializers that were automatically included in the constructor?
I answered that in a gist comment. Yes, I like the CoffeeScript constructor(@x, @y){} shorthand. Dart picked up on it, but requires this. instead of @. I'll put it in the gist.
A comment in your gist (lines 186-187) seems to say that with 'private x', we can use @x or this[x]. Is that really what you meant?
Sorry, not quite. I will fix that comment.
Plain 'x' is bound to the Name object and @x is the value of the property with that name? Is x a variable?
x is a const binding in the "@ scope" of the class body. It does not collide with any lexically bound x (formal parameter name, e.g.). That's important -- see the Monster constructor with its name and health parameters and private property names.
So, o at x is not o[x] for such a private-declared x.
Some argue @ should work for any x, so if there were some x = "y" in scope, then o at x would be o[x] would be o.y. I think that's too error-prone. What if I typo @heath instead of @health and there's a non-private-declared heath in scope?
Does this mean that the class desugars into something that has a let statement surrounding the all the methods?
No, because private x does not bind a lexical name 'x' usable freely.
private/@ is new, the semantics are a slight extension to today's, not desugarable without gensym. I'll consult with my semantic betters on this one.
One nit: you're using @ as both a sigil in @name and as an operator in other at name. I would expect other. at name instead.
No, it's a prefix operator if in operand context (like / starting a regexp), a binary operator in operator context (/ as division).
Trying to avoid .@ all over, and this.@ noise.
I think ordinary unary @ would dominate and .@ would be relatively rare. But when used, it would at least look like noisy property access rather than an email address. My gut says that regularity of syntax wins over conciseness and noise here.
Oh, I see -- you are suggesting @x as short for this. at x, and requiring other. at x for non-this-based references. That could work. IINM it even avoids the [no LineTerminator here] to the left of unary prefix @. Nice!
I hear you on the email thing. I'm big on grep, and I mourn the coming demise of codesearch.google.com.
Do you expect the performance of private property lookup to be equivalent to public property lookup? That is will @foo take the same time as this.foo?
That is a requirement in my view.
Le 02/11/2011 19:00, Brendan Eich a écrit :
On Nov 2, 2011, at 10:35 AM, David Bruant wrote:
Le 02/11/2011 18:09, Brendan Eich a écrit :
On Nov 2, 2011, at 4:10 AM, David Bruant wrote:
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea. Private names do not leak via reflection, not even via proxies. So what's the problem? My problem is that private names are per-class instead of per-instance. See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars.
Ok, that's what I was missing. What were the rationale? use cases?
You can make instance-private ivars yourself in the constructor using Name.create, and go to town. Knock yourself out! (I mean that in a good way, at least from where I sit :-P).
Hmm... Are you sure you can implement instance-private variables with no leak? Within the constructor, you can call Name.create, but where do you store the name? If in a constructor local variable, it's not usable anywhere else which is useless. If in a private class variable... well... that's not an instance-private name anymore. The private class variable could be a WeakMap indexed on instances, but it means that any instance with a reference to another instance can access the "private" value of the latter. That's not private. Am I missing something? If I can implement them myself, I promise, i'll knock myself out to town (damn, it's hard being a non-native English speaker. So many expressions I'm never sure I fully understand)
Is "other" a monster? (how do you "recognize" a monster from any other object?). You could do ad-hoc type or shape tests. For the example, and even in most cases in general, there's no need. Duck typing works with private names too. Who is "you" in your case? My "you" was "runtime". I think runtime shouldn't do heuristic-tests like comparing shapes. We don't have types. I have no idea what you are proposing, but my point stands: there's no need to recognize another Monster and no capability leak. The code works as much JS today does with public property names.
Ok. I thought @health refered to a per-instance name. Now I know it's a per-class name, other at health makes sense.
On Nov 2, 2011, at 10:52 AM, Quildreen Motta wrote:
On 02/11/11 15:42, Erik Corry wrote:
2011/11/2 Quildreen Motta<quildreen at gmail.com>:
I don't think hard coding the name of the super-constructor is a problem. It is when you take into account that functions in JavaScript are not bound to an object, they are generic. You can simply assign any function to any object and it'll most likely just work. I think the chances are slim that you can take a function that does a super call, put it on a different object, and it will 'just work'. It's a pretty rare case. "most likely". Also, it should work if |super| were dynamically resolved, as long as the object implemented what's required by the call.
We can't do dynamic super without adding a parameter to every function call everywhere (modulo optimization), or simulating Algolish nested scopes using dynamic scope hacks, or worse. This is something we won't do, I'm pretty sure Erik agrees.
The super proposal in Harmony is static, but you can Object.defineMethod to transplant (a method clone with different [[Super]], of course -- again modulo optimizations). [[Super]] is an internal property of function objects. I believe Allen is correct that this is how dynamic OO languages in general support 'super'.
On Nov 2, 2011, at 11:17 AM, David Bruant wrote:
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars. Ok, that's what I was missing. What were the rationale? use cases?
The rationale is that most mainstream OO languages with the most users support class-private not instance-private ivars. Sorry, Smalltalkers!
Use cases are all around us in JS today, using public properties. Refactoring to private should not require rewriting to add getter/setter method, etc.
You can make instance-private ivars yourself in the constructor using Name.create, and go to town. Knock yourself out! (I mean that in a good way, at least from where I sit :-P). Hmm... Are you sure you can implement instance-private variables with no leak?
You'd need to use the closure pattern:
class BnD { constructor(x) { const my_x = Name.create('x'); this[my_x] = x; this.method1 = function (...) {...}; ... this.methodN = function (...) {...}; } }
The method1..N functions can use my_x, no one else can.
On 02/11/11 16:21, Brendan Eich wrote:
On Nov 2, 2011, at 10:52 AM, Quildreen Motta wrote:
On 02/11/11 15:42, Erik Corry wrote:
2011/11/2 Quildreen Motta<quildreen at gmail.com>:
I don't think hard coding the name of the super-constructor is a problem. It is when you take into account that functions in JavaScript are not bound to an object, they are generic. You can simply assign any function to any object and it'll most likely just work. I think the chances are slim that you can take a function that does a super call, put it on a different object, and it will 'just work'. It's a pretty rare case. "most likely". Also, it should work if |super| were dynamically resolved, as long as the object implemented what's required by the call. We can't do dynamic super without adding a parameter to every function call everywhere (modulo optimization), or simulating Algolish nested scopes using dynamic scope hacks, or worse. This is something we won't do, I'm pretty sure Erik agrees.
Ah yeah, I'm well aware that dynamic super isn't happening.
Le 02/11/2011 19:29, Brendan Eich a écrit :
On Nov 2, 2011, at 11:17 AM, David Bruant wrote:
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars. Ok, that's what I was missing. What were the rationale? use cases?
The rationale is that most mainstream OO languages with the most users support class-private not instance-private ivars. Sorry, Smalltalkers!
I thought Java had per-instance private properties, but not, it's per-class as well. Interesting.
You can make instance-private ivars yourself in the constructor using Name.create, and go to town. Knock yourself out! (I mean that in a good way, at least from where I sit :-P). Hmm... Are you sure you can implement instance-private variables with no leak?
You'd need to use the closure pattern:
class BnD { constructor(x) { const my_x = Name.create('x'); this[my_x] = x; this.method1 = function (...) {...}; ... this.methodN = function (...) {...}; } }
The method1..N functions can use my_x, no one else can.
Ok. Perfect. It's unfortunate to loose the syntactic sugar ("this.method1 = function(){};" and compulsory explicit |this| in the function body), but it's doable.
Thanks,
On Nov 2, 2011, at 11:29 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 2, 2011, at 11:17 AM, David Bruant wrote:
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars. Ok, that's what I was missing. What were the rationale? use cases?
Doesn't the latest harmony class proposal define private within the constructor? I assume this proposal would supersede the Nov 2008 meeting Grammar pasted below:
CallExpression : ... private ( AssignmentExpression ) ConstructorElement : ... PrivateVariableDefinition PrivateVariableDefinition : private ExportableDefinition
The rationale is that most mainstream OO languages with the most users support class-private not instance-private ivars. Sorry, Smalltalkers!
In java one cannot access a private declaration in this way without defining a protected getter/setter for example.
Use cases are all around us in JS today, using public properties. Refactoring to private should not require rewriting to add getter/setter method, etc.
But if you are locking down a private var, isn't refactoring implied by your statement 'avoid ... hostile attacks, on private data'. Private instance vars would, for example, prevent the following:
class Account { private balance = 0; constructor(balance) { @balance = balance; } compare(otheraccount) { if(otheraccount at balance > 1000000) { @balance += --otheraccount at balance; } if(@balance > otheraccount at balance) {
return 1; } else if(@balance < otheraccount at balance) { return -1; } return 0; } }
You can make instance-private ivars yourself in the constructor using Name.create, and go to town. Knock yourself out! (I mean that in a good way, at least from where I sit :-P). Hmm... Are you sure you can implement instance-private variables with no leak?
You'd need to use the closure pattern:
class BnD { constructor(x) { const my_x = Name.create('x'); this[my_x] = x; this.method1 = function (...) {...}; ... this.methodN = function (...) {...}; } }
The method1..N functions can use my_x, no one else can.
In Crockford terminology, method1..N are 'privileged' functions, but come at the cost of memory allocation per object. Does class-private ivars enable the private names proposal? Eg, is there an implicit dependency?
On Nov 2, 2011, at 1:17 PM, Kam Kasravi wrote:
On Nov 2, 2011, at 11:29 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 2, 2011, at 11:17 AM, David Bruant wrote:
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars. Ok, that's what I was missing. What were the rationale? use cases?
Doesn't the latest harmony class proposal define private within the constructor? I assume this proposal would supersede the Nov 2008 meeting Grammar pasted below:
Are you asking a procedural question, or something? If so, bzzzt. :-|
I'm hacking a gist forked from Jeremy's. But TC39 is sticking to consensus where we can. The wiki'ed class proposal does have class-private instance variables. It simply mislocates the private declaration inside the constructor. Again, this proposal is in trouble and the gist'ing is an attempt to rescue it, outside the confines of the somewhat-overconstrained TC39 setting.
To bring the comments back around a little bit to this original proposal.
One thing I can't get over with this proposal is how obvious the syntax makes the semantics. I didn't actually have to read any of the descriptions to figure out how all of this works, my mind made a set of assumptions and they turned out to be correct.
While some of the alternate proposals shave the syntax in terms of bytes they are non-obvious and I'd happily take the hit in bytes for the simplicity and lack of explanation the original proposal has.
This is on par with the destructuring proposal in terms of obviousness which I felt the previous class proposal lacked.
This also feels a lot like JavaScript, like the syntax was already hiding out waiting to be used, it doesn't few "new", if that makes sense.
var generateModelClass = function(columns) {
var definition = {};
columns.forEach(function(col) { definition['get' + col] = function() { return this[col]; }; definition['set' + col] = function(value) { return this[col] = value; }; });
return class definition;
};
That is exactly what I would write if I was guessing how to generate dynamic classes.
In fact, all the examples in this proposal feel significantly more dynamic and less declarative than in strawman:classes_as_sugar .
My 2 cents.
On Nov 2, 2011, at 1:20 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 2, 2011, at 1:17 PM, Kam Kasravi wrote:
On Nov 2, 2011, at 11:29 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 2, 2011, at 11:17 AM, David Bruant wrote:
See my reply to Kam. We're not sugaring instance-private ivars. I am proposing something we agreed to in Nov. 2008: sugaring class-private ivars. Ok, that's what I was missing. What were the rationale? use cases?
Doesn't the latest harmony class proposal define private within the constructor? I assume this proposal would supersede the Nov 2008 meeting Grammar pasted below:
Are you asking a procedural question, or something? If so, bzzzt. :-|
I'm hacking a gist forked from Jeremy's. But TC39 is sticking to consensus where we can. The wiki'ed class proposal does have class-private instance variables. It simply mislocates the private declaration inside the constructor. Again, this proposal is in trouble and the gist'ing is an attempt to rescue it, outside the confines of the somewhat-overconstrained TC39 setting.
Understood, just wanted to clarify exactly what the TC39 consensus was in respect to the harmony class proposal. I do think your gist has advantages over the harmony class proposal both in terms of removing the public keyword and clarifying private-class var declaration and semantics. My only concern would be users/framework writers opting for the closure pattern in lieu of using private to prevent the Account use case I noted. That is, I would normally interpret private to mean no access unless you're the instance and within class scope. Here private means no access unless you're any instance and within class scope.
On Wed, Nov 2, 2011 at 13:39, Mikeal Rogers <mikeal.rogers at gmail.com> wrote:
var generateModelClass = function(columns) {
var definition = {};
columns.forEach(function(col) { definition['get' + col] = function() { return this[col]; }; definition['set' + col] = function(value) { return this[col] = value; }; });
return class definition;
};
I feel like this is too strange to my liking. With something more along what Brendan was suggesting the same could be done like this:
var generateModelClass = function(columns) { var c = class {}, p = returnClass.prototype;
columns.forEach(function(col) { p['get' + col] = function() { return this[col]; }; p['set' + col] = function(value) { return this[col] = value; }; });
return c; };
I guess we just don't agree then, I find this alternative very unintuitive.
On 11/2/11 11:16 AM, Brendan Eich wrote:
On Nov 2, 2011, at 10:52 AM, David Flanagan wrote:
Could they have initializers that were automatically included in the constructor?
I answered that in a gist comment. Yes, I like the CoffeeScript constructor(@x, @y){} shorthand. Dart picked up on it, but requires this. instead of @. I'll put it in the gist.
I saw the comment, I think. What I meant here was initializer expressions that are part of the 'private' declaration. That is, can I write
private stack = [];
and have that initialization code automatically inserted (Java-like) into the constructor for me? I'm trying to understand whether private and static have the same basic syntax in this proposal.
Plain 'x' is bound to the Name object and @x is the value of the property with that name? Is x a variable?
x is a const binding in the "@ scope" of the class body. It does not collide with any lexically bound x (formal parameter name, e.g.). That's important -- see the Monster constructor with its name and health parameters and private property names.
So, o at x is not o[x] for such a private-declared x.
What I don't understand is whether the "@ scope" is a specification abstraction, or whether it is visible to programmers. If I declare a private variable x, and there isn't any other x in scope, is x bound to a Name object that I can use?
If not, is there any way I can access the automatically-created Name objects so that I can share my private names with "friend" classes?
Some argue @ should work for any x, so if there were some x = "y" in scope, then o at x would be o[x] would be o.y. I think that's too error-prone. What if I typo @heath instead of @health and there's a non-private-declared heath in scope?
That's where a 'public' declaration comes in! 'private x' binds x to a new Name in @ scope. And 'public y' binds y to "y" in @ scope. Then make @ work with any identifier declared private or public. That would allow programmers to elide the "this" but still have publicly visible properties.
Resolution of identifiers in @ scope can happen at compile time, can't it? That is, for the code "@x", x is just looked up in @ scope once when the code is compiled, not dynamcally each time it is evaluated, right?
On Nov 2, 2011, at 3:08 PM, David Flanagan wrote:
On 11/2/11 11:16 AM, Brendan Eich wrote:
On Nov 2, 2011, at 10:52 AM, David Flanagan wrote:
Could they have initializers that were automatically included in the constructor?
I answered that in a gist comment. Yes, I like the CoffeeScript constructor(@x, @y){} shorthand. Dart picked up on it, but requires this. instead of @. I'll put it in the gist.
I saw the comment, I think. What I meant here was initializer expressions that are part of the 'private' declaration. That is, can I write
private stack = [];
and have that initialization code automatically inserted (Java-like) into the constructor for me? I'm trying to understand whether private and static have the same basic syntax in this proposal.
The problem with initialization of private vars outside of the constructor is the order dependency. Do private names with initializers get bound and initialized before entry to the constructor, independent of their order with respect to the constructor in the class body? I hope so, but that might seem confusing. Can an initializer refer to the value of a prior name, e.g.
private stack = [], stack2 = @stack;
My gist does not show initializers for private names, and since I switched to object literal syntax with optional prefix keywords, the above should be
private stack: [], private stack2: @stack, ...
That is, no ; at end, and private must be repeated as a property initialiser prefix (which sucks).
An alternative mentioned in some comments is to allow @name: value, and @method() {...}, to be used in class bodies to bind private names without having to pre-declare them with private name, method; declarations or the like. That avoids the problem but only if you are willing to initialize prototype private-named properties -- that's what these initialiser parts do.
If you want to declare private names in scope throughout the body (in all methods), initailized in the constructor via @name = value; assignment expression-statements, then you want something like a private name; declaration. But the declaration-style syntax, with semicolon at end, is at odds with the main object literal flow.
Plain 'x' is bound to the Name object and @x is the value of the property with that name? Is x a variable?
x is a const binding in the "@ scope" of the class body. It does not collide with any lexically bound x (formal parameter name, e.g.). That's important -- see the Monster constructor with its name and health parameters and private property names.
So, o at x is not o[x] for such a private-declared x.
What I don't understand is whether the "@ scope" is a specification abstraction, or whether it is visible to programmers. If I declare a private variable x, and there isn't any other x in scope, is x bound to a Name object that I can use?
Only via @ -- not in other expressions or you collide @health and health.
If not, is there any way I can access the automatically-created Name objects so that I can share my private names with "friend" classes?
No, not in what I'm proposing. If you want that, you'll have to Name.create().
Some argue @ should work for any x, so if there were some x = "y" in scope, then o at x would be o[x] would be o.y. I think that's too error-prone. What if I typo @heath instead of @health and there's a non-private-declared heath in scope?
That's where a 'public' declaration comes in! 'private x' binds x to a new Name in @ scope. And 'public y' binds y to "y" in @ scope. Then make @ work with any identifier declared private or public. That would allow programmers to elide the "this" but still have publicly visible properties.
Ok, that seems unproblematic at first glance.
Resolution of identifiers in @ scope can happen at compile time, can't it?
Requirement!
That is, for the code "@x", x is just looked up in @ scope once when the code is compiled, not dynamcally each time it is evaluated, right?
Absolutely.
Updated: gist.github.com/1332193
$ git commit -a -m"Updates per conversations with @dflanagan and @allenwb."
git diff output below. Exec. summary: prefix groups via { ... } and public @-namespace population to avoid "this." sad-making verbosity.
/be
diff --git a/minimalist-classes.js b/minimalist-classes.js index 18a7a8c..6f25144 100644 --- a/minimalist-classes.js +++ b/minimalist-classes.js @@ -188,8 +188,11 @@ class Monster { // See the new sameName method for an example of infix and prefix @ in action. // // David Flanagan suggests keeping the unary-prefix @foo shorthand, but making -// the long-hand obj. at foo. Breaks E4X but no matter -- I'm open to it, but it -// is not essential to bikeshed here. +// the long-hand obj. at foo. Breaks E4X but no matter -- but the win is that the +// [no LineTerminator here] restriction on the left of unary-prefix @ is not +// needed. Also the references don't grep like email addresses, which counts +// with David and me. So I've incorporated David's suggestion. See other. at name +// etc. below. // // There is no const instance variable declaration. Non-writable instance vars // (properties to most people) are quite rare. Use ES5's Object.defineProperty @@ -203,15 +206,17 @@ class Monster {
class Monster {
- private name, health;
-
private { name, health }, // can group a prefix, same for const and static
-
public flair, // you should have 37 pieces of public flair
constructor(name, health) { @name = name; @health = health;
-
@flair = 0; }
sameName(other) {
- return @name === other at name;
- return @name === other. at name; }
private attackHelper(target) {
On Nov 2, 2011, at 7:00 PM, Brendan Eich wrote:
Updated: gist.github.com/1332193
$ git commit -a -m"Updates per conversations with @dflanagan and @allenwb."
git diff output below. Exec. summary: prefix groups via { ... } and public @-namespace population to avoid "this." sad-making verbosity.
/be
diff --git a/minimalist-classes.js b/minimalist-classes.js index 18a7a8c..6f25144 100644 --- a/minimalist-classes.js +++ b/minimalist-classes.js @@ -188,8 +188,11 @@ class Monster { // See the new sameName method for an example of infix and prefix @ in action. // // David Flanagan suggests keeping the unary-prefix @foo shorthand, but making -// the long-hand obj. at foo. Breaks E4X but no matter -- I'm open to it, but it -// is not essential to bikeshed here. +// the long-hand obj. at foo. Breaks E4X but no matter -- but the win is that the +// [no LineTerminator here] restriction on the left of unary-prefix @ is not +// needed. Also the references don't grep like email addresses, which counts +// with David and me. So I've incorporated David's suggestion. See other. at name +// etc. below. // // There is no const instance variable declaration. Non-writable instance vars // (properties to most people) are quite rare. Use ES5's Object.defineProperty @@ -203,15 +206,17 @@ class Monster {
class Monster {
- private name, health;
private { name, health }, // can group a prefix, same for const and static
public flair, // you should have 37 pieces of public flair
constructor(name, health) { @name = name; @health = health;
@flair = 0; }
sameName(other) {
- return @name === other at name;
- return @name === other. at name; }
private attackHelper(target) {
Quick followup:
$ git diff diff --git a/minimalist-classes.js b/minimalist-classes.js index 6f25144..7d8195e 100644 --- a/minimalist-classes.js +++ b/minimalist-classes.js @@ -206,7 +206,10 @@ class Monster {
class Monster {
- private { name, health }, // can group a prefix, same for const and static
-
private { name, health } // can group a prefix, same for const and static
-
// note comma optional after closing brace, as for
-
// method definitions
-
public flair, // you should have 37 pieces of public flair
constructor(name, health) { $ git commit -a -m"More comma elision." [master 4f47332] More comma elision. 1 files changed, 4 insertions(+), 1 deletions(-)
Last followup for tonight:
$ git diff diff --git a/minimalist-classes.js b/minimalist-classes.js index 7d8195e..d6bdab2 100644 --- a/minimalist-classes.js +++ b/minimalist-classes.js @@ -212,9 +212,9 @@ class Monster {
public flair, // you should have 37 pieces of public flair
- constructor(name, health) {
- @name = name;
- @health = health;
- // Yes, shorthand for @name = name given formal param name, etc. -- just as
- // in CoffeeScript!
- constructor(@name, @health) { @flair = 0; }
$ git commit -a -m"constructor(@foo) shorthand." [master 7d1228f] constructor(@foo) shorthand. 1 files changed, 3 insertions(+), 3 deletions(-)
I'm totally in agreement with Jeremy's "class id objectLiteral" proposal, but for one thing: don't give up on your function definition syntax yet!
I really don't see any reason for adding a new way to define function (methods). This:
class Runner { run(a) { } }
is just as clearly expressed as this:
class Runner { run: function(a) { } }
Granted, if we went with the latter, there's slightly more to type BUT 1) it's immediately clear to all JavaScript developers what's going on and how to work with it and 2) there are already several good proposals for function definition shorthands (arrow syntax and block lambdas). As I said on the github thread, we need to take a holistic view here and not lose sight of its effects on the rest of the language. For example, with block lambdas:
class Runner { run: {| a | } }
Introducing yet another syntax for creating a function feels like bloat to me. Also, I bet we would start seeing a lot of "run(a){…}" attempts at function definitions in a non-class context.
On Nov 2, 2011, at 10:38 PM, Matthew Tretter wrote:
Introducing yet another syntax for creating a function feels like bloat to me.
It's not just for creating a function. Method definition syntax was proposed a while ago and it makes the method non-enumerable and binds super correctly. Please don't treat it as just short for ': function' (although that is approximately 10 chars too long at scale :-|).
So to clarify, is the dynamic super issue the whole reason that Jeremy's dynamic construction of classes is considered not doable? Because it seems to me that super may not be worth that trade off. Besides, Python's super implementation requires the hardcoding of the class and that doesn't cause much of a stink. If something similar would give us this super-intuitive syntax and the ability to build classes from arbitrary object literals, it seems like not a big loss.
What is "super-intuitive" about running 'class C' up against an arbitrary expression, which is then evaluated and copied (details fuzzy here) as the class prototype?
Arguments about feelings and intuition are not that helpful. Saying why you need to construct a class that way, where no such object copying primitive exists in JS, would be more helpful. IOW, what's the use-case?
I noticed the absence of setter's, getter's. Would this be valid syntax?
set health(value) { if (value < 0) { throw new Error("Health must be non-negative."); } @health = value; }
Sorry, I'll try to be more clear.
What's "super-intuitive" isn't that you use the form "class name expr", it's how you interact with that form once you know what it does. The reason is self-evident—people know how to work with object literals and functions. This is not true of the Leather form which, like I said, would probably inspire a lot of "run(a){…}" attempts at function definitions in non-class contexts. Not that that alone is enough to disqualify it, but it's something that should be taken into consideration.
Off the top of my head, one use-case would be Python-like method decorators:
class Runner {
run: require_auth(function(a) {
})
}
Another would be the dynamic definition of methods:
class Runner {
run: (function() {
return someFeatureIsSupported ? feature : polyfill;
})()
}
There are many more, I'm sure, but the point is this: a syntax that makes use of the elements already in the language (instead of providing alternates) is going to be more familiar and therefore objectively more intuitive. Yes, the class keyword brings some new wrinkles, but that will be true of any proposal.
There are many more, I'm sure, but the point is this: a syntax that makes use of the elements already in the language (instead of providing alternates) is going to be more familiar and therefore objectively more intuitive. Yes, the class keyword brings some new wrinkles, but that will be true of any proposal.
I made a proposal on this issue a couple hours before brendan's and I'm afraid it got lost in the shuffle (or maybe it was just bad enough it didn't deserve response ;) - I posted it to gist.github.com/1332028
Here's the tl;dr -
I'm basically taking Jeremy's proposal and exploring the mechanics of it a little deeper. I attempt to unify class extends and the <| operator. I also try to tackle the "evaluated and copied (details fuzzy here) as the class prototype" problem.
On Nov 3, 2011, at 1:23 AM, Kam Kasravi wrote:
I noticed the absence of setter's, getter's. Would this be valid syntax?
Yes, that's already in object literal syntax.
My gist is a fork of Jeremy's, he didn't add 'em so I didn't either. At this point they are context, assumed. They're in ES5!
On Nov 3, 2011, at 6:53 AM, Matthew Tretter wrote:
Sorry, I'll try to be more clear.
What's "super-intuitive" isn't that you use the form "class name expr", it's how you interact with that form once you know what it does. The reason is self-evident—people know how to work with object literals and functions.
Then we are not talking about the same thing.
Class syntax of the form class C {...} where the {...} is an extension of ES3-5 ObjectLiteral syntax is fine, we're aligned on that (for the moment).
The particular form from Jeremy's gist:
// Note that the right-hand side of a class definition is just an expression, // an object literal is not required. You can be fully dynamic when creating a // class:
class Student objectContainingStudentProperties line 59 on at gist.github.com/1329619
is what is at issue.
This is not true of the Leather form which, like I said, would probably inspire a lot of "run(a){…}" attempts at function definitions in non-class contexts.
Different topic yet again, but ok: method definition syntax was already proposed a while ago by Allen, and promoted to Harmony for ES.next. It may cause some Dart-like 'function'-lacking misplaced method definition attempts, indeed. We should see how big a problem this is in practice. I doubt it'll be more than a speed-bump for some, but if it is, we can adapt.
Not that that alone is enough to disqualify it, but it's something that should be taken into consideration.
Agreed.
Off the top of my head, one use-case would be Python-like method decorators:
class Runner { run: require_auth(function(a) { }) }
Another would be the dynamic definition of methods:
class Runner { run: (function() { return someFeatureIsSupported ? feature : polyfill; })() }
These are still legal, with class body built on extended object literals. Why did you think these would be verboten?
It's one thing to say "these should work". It's another to insist that the long-hand should be the only way.
On 11/02/2011 04:10 AM, David Bruant wrote:
Another topic:
class Monster {
private name, health;
sameName(other) { return @name === other at name; } }
I am under this impression that you are accessing the private property ("other at name") of an instance which isn't you (other !== this) and I'm not sure it's a good idea. Is "other" a monster? (how do you "recognize" a monster from any other object?). If so, is it a good enough reason for you to be able to access its private state? If other is not a monster, what is the behavior?
Without guards you don't know that "other" is a monster.
The above simple case works because "other" won't have an @name property. However, once you consider assignments, you do have a problem:
class Monster {
private name;
writeName(other) { other at name = "cookie"; }
... sameName ... }
Now you can create @name properties on any object. Depending on which of the myriad of class proposals you're considering, you might not even need a method that writes to other at name -- if you can extract a method from a class and give it a different "this", then all you'd need would be any method that writes to @name.
Waldemar
Sorry have not had a chance to reply on this thread earlier. I do really like the direction that Jeremy pushes to, but still I don't understand why do we need to introduce new syntax to the language. I think class
expression is completely unnecessary, why not a function ? I forked Jeremy's proposal and modified it so that it preserves it's simplicity without introducing any new syntax to the language:
In addition I changed few things that was raising additional questions:
constructor
property as initializer (what if constructor is shared, frozen etc).
I also intentionally omitted super
as it's separate topic.
-- Irakli Gozalishvili Web: www.jeditoolkit.com Address: 29 Rue Saint-Georges, 75009 Paris, France (goo.gl/maps/3CHu)
'Evening, ES-Discuss.
After poking a stick in the bees' nest this morning (apologies, Allen), and in the spirit of loyal opposition, it's only fair that I throw my hat in the ring.
Here is a proposal for minimalist JavaScript classes that enable behavior that JavaScripters today desire (as evidenced by libraries and languages galore), without adding any new semantics beyond what already exists in ES3.
gist.github.com/1329619
Let me know what you think, and feel free to fork.