class sugar: static inheritance
On Sun, Jun 5, 2011 at 9:35 PM, Peter Michaux <petermichaux at gmail.com>wrote:
Are static members inherited? What happens in the last line of the following code?
class Monster { static allMonsters = [];
constructor() { Monster.allMonsters.push(this); }
}
class Dragon extends Monster { constructor() {} }
new Monster(); Monster.allMonsters.length; // 1 Dragon.allMonsters.length; // 0, 1, or error?
Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined.
Correct. And the desugaring you show below is correct. This question is listed as an open issue at the bottom of < harmony:classes#open_issues>, so it's
possible we'd change our minds. I can see both sides on this one. But on the whole, I think we are better off sticking with the current design. A tremendous amount of old code manually encodes classes into prototypes using the common pattern that the current proposal desugars into.
Once the authors of that old code are no longer worried about pre-ES6 platforms, were classes to adopt static inheritance, then:
When old code using the common pattern is manually translated in the obvious way to classes, its meaning will change in non-obvious ways.
Of all the pros and cons for this issue, this seems like a compelling con.
That is, static members are not inherited at all which does not seem very class-like.
Depends on what classes tradition you come from. There are languages on both sides of this dichotomy.
On Sun, Jun 5, 2011 at 9:35 PM, Peter Michaux <petermichaux at gmail.com>wrote:
Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined.
That's correct. Do you have any examples of code where inheriting the constructor objects would be helpful?
On Jun 6, 2011, at 10:19 AM, Bob Nystrom wrote:
On Sun, Jun 5, 2011 at 9:35 PM, Peter Michaux <petermichaux at gmail.com> wrote: Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined.
That's correct. Do you have any examples of code where inheriting the constructor objects would be helpful?
Used all the time in Ruby, and in some Smalltalks. Supported by CoffeeScript. See
gist.github.com/1007150, gist.github.com/1006999
(warning: Ruby reading skills required).
On Jun 6, 2011, at 10:32 AM, Brendan Eich wrote:
On Jun 6, 2011, at 10:19 AM, Bob Nystrom wrote:
On Sun, Jun 5, 2011 at 9:35 PM, Peter Michaux <petermichaux at gmail.com> wrote: Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined.
That's correct. Do you have any examples of code where inheriting the constructor objects would be helpful?
Used all the time in Ruby, and in some Smalltalks. Supported by CoffeeScript. See
Actually, all modern Smalltalks where modern means anything post 1980
Just as the private(this) is used to expose the private instance record, could static([[Constructor]]) be introduced to provide access to the union of static properties across the set of constructor objects? (I know that their is some concern about the lengthy syntax of private())
Regarding the use of private(AssignmentExpression), does it provide access to all private ExportableDefinition's regardless of what inherited class they were declared in? That is given class Monster { constructor(health) { private health = health; } }
class GilaMonster prototype Monster { constructor(owner) { super(10); private owner = owner; } eat(critter) { If(critter === private(this).owner) { private(this).health = 0; } else { private(this).health++; } } }
Should the GilaMonster's private record include health?
On Jun 8, 2011, at 2:45 AM, Kam Kasravi wrote:
Just as the private(this) is used to expose the private instance record,
Please read
private(this), e.g., is unbearably verbose; leaks an implementation detail. from harmony:classes#open_issues.
The syntax is a placeholder. It will not last.
could static([[Constructor]]) be introduced to provide access to the union of static properties across the set of constructor objects? (I know that their is some concern about the lengthy syntax of private())
It's not "concern", it is an open issue to resolve by using better syntax.
What does "union of static properties across the set of constructor objects" mean? We're not reifying sets of properties as objects, if that's what you mean (static private yikes).
I can see how private(foo) brings this to mind. This is the problem I clumsily labeled "leaks an implementation detail".
Regarding the use of private(AssignmentExpression), does it provide access to all private ExportableDefinition's regardless of what inherited class they were declared in? That is given class Monster { constructor(health) { private health = health; } }
class GilaMonster prototype Monster { constructor(owner) { super(10); private owner = owner; } eat(critter) { If(critter === private(this).owner) { private(this).health = 0; } else { private(this).health++; } } }
Should the GilaMonster's private record include health?
The "private record" especially views as an "object" is a kind of placeholder too. It doesn't help to think of it as a record. It probably does not help to think of it as an object, either, especially if you cannot get at it via anything like the still-straw private(this) or private(other) syntax.
On another topic, it does seem like "protected" is inevitable, doesn't it?
On Jun 8, 2011, at 3:05 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 8, 2011, at 2:45 AM, Kam Kasravi wrote:
Just as the private(this) is used to expose the private instance record,
Please read
private(this), e.g., is unbearably verbose; leaks an implementation detail. from harmony:classes#open_issues.
The syntax is a placeholder. It will not last.
could static([[Constructor]]) be introduced to provide access to the union of static properties across the set of constructor objects? (I know that their is some concern about the lengthy syntax of private())
It's not "concern", it is an open issue to resolve by using better syntax.
What does "union of static properties across the set of constructor objects" mean? We're not reifying sets of properties as objects, if that's what you mean (static private yikes).
This was related to Peter's concern included below for brevity
Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined.
I was wondering if this could be solved in some way without explicitly referencing the particular Constructor. This isn't a static private issue per se, rather a suggestion for an operator that would allow one to reference Monster.allMonsters as Dragon.allMonsters. eg static(Dragon).allMonsters. Although it would probably be more consistent to resolve Dragon.allMonsters in the same way properties on the prototype chain are resolved. So we can probably ignore this suggestion.
I can see how private(foo) brings this to mind. This is the problem I clumsily labeled "leaks an implementation detail".
Regarding the use of private(AssignmentExpression), does it provide access to all private ExportableDefinition's regardless of what inherited class they were declared in? That is given class Monster { constructor(health) { private health = health; } }
class GilaMonster prototype Monster { constructor(owner) { super(10); private owner = owner; } eat(critter) { If(critter === private(this).owner) { private(this).health = 0; } else { private(this).health++; } } }
Should the GilaMonster's private record include health?
The "private record" especially views as an "object" is a kind of placeholder too. It doesn't help to think of it as a record. It probably does not help to think of it as an object, either, especially if you cannot get at it via anything like the still-straw private(this) or private(other) syntax.
Yes, I agree, I was attempting to keep as close as possible to http terminology used in the proposal.
On another topic, it does seem like "protected" is inevitable, doesn't it?
Which may be a slippery slope :( BTW is it premature to ask questions on harmony proposals in their current state?
Sorry iPad autocorrection strikes again... The comment towards the bottom should read:
On Wed, Jun 8, 2011 at 3:05 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 8, 2011, at 2:45 AM, Kam Kasravi wrote:
Just as the private(this) is used to expose the private instance record,
Please read
private(this), e.g., is
- unbearably verbose;
- leaks an implementation detail.
from harmony:classes#open_issues.
The syntax is a placeholder. It will not last.
could static([[Constructor]]) be introduced to provide access to the union of static properties across the set of constructor objects? (I know that their is some concern about the lengthy syntax of private())
It's not "concern", it is an open issue to resolve by using better syntax.
What does "union of static properties across the set of constructor objects" mean? We're not reifying sets of properties as objects, if that's what you mean (static private yikes).
I can see how private(foo) brings this to mind. This is the problem I clumsily labeled "leaks an implementation detail".
Regarding the use of private(AssignmentExpression), does it provide access to all private ExportableDefinition's regardless of what inherited class they were declared in? That is given *class Monster { *
- constructor(health) { *
- private health = health; *
- }* *} *
*class GilaMonster prototype Monster { constructor(owner) { *
- super(10); *
- private owner = owner;*
- }*
- eat(critter) { *
- If(critter === private(this).owner) {*
private(this).health = 0; *
- } else {*
private(this).health++;*
- }*
- }* }
Should the GilaMonster's private record include health?
The "private record" especially views as an "object" is a kind of placeholder too. It doesn't help to think of it as a record. It probably does not help to think of it as an object, either, especially if you cannot get at it via anything like the still-straw private(this) or private(other) syntax.
On another topic, it does seem like "protected" is inevitable, doesn't it?
Actually, it isn't another topic. It's why Kam's GilaMonster cannot see Monster's "health". "private" means "private to the class", not "private to the class and subclasses". "protected" would mean the second, and no, I don't think it's inevitable.
E and Caja have been doing very well with "private", "public", and inheritance, and I've only ever been frustrated by the lack of "protected" once in a blue moon. And it was always easy to work around its absence. Given ES-next's private names and weak maps, it becomes even easier to work around the absence of "protected". Btw, in both C++ and Java, the semantics of "protected" is a mess, in different ways.
On Wed, Jun 8, 2011 at 3:41 AM, Kam Kasravi <kamkasravi at yahoo.com> wrote:
Which may be a slippery slope :( BTW is it premature to ask questions on harmony proposals in their current state?
Hi Kam, not at all. Such questions are welcomed, and will provoke us to clarify these proposals quickly. Thanks.
On Jun 8, 2011, at 3:41 AM, Kam Kasravi wrote:
I was wondering if this could be solved in some way without explicitly referencing the particular Constructor. This isn't a static private issue per se, rather a suggestion for an operator that would allow one to reference Monster.allMonsters as Dragon.allMonsters. eg static(Dragon).allMonsters. Although it would probably be more consistent to resolve Dragon.allMonsters in the same way properties on the prototype chain are resolved. So we can probably ignore this suggestion.
No, you're verging on the @ as private member access operator idea. For class-private instance variables we need something like private(this) and private(other), or else this at x and other at x given private member x. A shorthand: @x is this at x (works even in the face of ASI if the binary o at x form has a [no LineTerminator here] restriction on the left of the @).
On another topic, it does seem like "protected" is inevitable, doesn't it?
Which may be a slippery slope :(
We are already on the slope, the question is traction of the "hold here" people vs. "it's fun, slip some more" users (some users will want protected).
BTW is it premature to ask questions on harmony proposals in their current state?
Sure.
On Jun 8, 2011, at 8:14 AM, Mark S. Miller wrote:
The "private record" especially views as an "object" is a kind of placeholder too. It doesn't help to think of it as a record. It probably does not help to think of it as an object, either, especially if you cannot get at it via anything like the still-straw private(this) or private(other) syntax.
On another topic, it does seem like "protected" is inevitable, doesn't it?
Actually, it isn't another topic. It's why Kam's GilaMonster cannot see Monster's "health".
My topic changed from "private record/object", 'sall ;-).
"private" means "private to the class", not "private to the class and subclasses". "protected" would mean the second, and no, I don't think it's inevitable.
It's not, we can choose to hold the line -- question below, though.
E and Caja have been doing very well with "private", "public", and inheritance, and I've only ever been frustrated by the lack of "protected" once in a blue moon. And it was always easy to work around its absence. Given ES-next's private names and weak maps, it becomes even easier to work around the absence of "protected".
You can make all kinds of friend relations with lexical scope and weak maps, it's true. But these can have the same cost problem that led to the private instance variable goal:
- An efficient implementation. The private state should be allocated with the instance as part of a single allocation, and with no undue burden on the garbage collector.
(from harmony:classes#private_instance_members).
Private name objects can be shared via lexical scope and used more efficiently to address a property on a base class from both base and subclass, where the property storage is conceptually packed, and implementations can do their best, as with any ad-hoc property added non-declaratively. But such properties will not be part of the declarative class instance state.
Having declarative syntax in the constructor body for public and private variables serves several goals, not just efficiency. Also, private name objects must be accessed using [] not dot.
We can say "for protected, use these patterns". But patterns are not always the best solution. As Peter Norvig said they often show bugs in one's programming language.
Btw, in both C++ and Java, the semantics of "protected" is a mess, in different ways.
Sure. Let's not make those mistakes. Non-fixable in your view?
On Wed, Jun 8, 2011 at 8:27 AM, Brendan Eich <brendan at mozilla.com> wrote:
Btw, in both C++ and Java, the semantics of "protected" is a mess, in different ways.
Sure. Let's not make those mistakes. Non-fixable in your view?
Not necessary in my view. There are all sorts of old constructs that address needs that turn out to be rare. Again, I've encountered an actual need for this only rarely, and only a mild need. As many people have said, language design largely consists of deciding to leave things out[1]. We should do more of that ;).
Many of the things we pulled from classes to make it simple enough to get accepted -- "requires"/"abstract", traits, earlier error checking, coupling to trademarks[2] -- are all more compelling that "protected".
On Wed, Jun 8, 2011 at 8:18 AM, Brendan Eich <brendan at mozilla.com> wrote:
On Jun 8, 2011, at 3:41 AM, Kam Kasravi wrote:
Which may be a slippery slope :(
We are already on the slope, the question is traction of the "hold here" people vs. "it's fun, slip some more" users (some users will want protected).
Count me as a "hold here"er ;).
[1] Who said that first? [2] Several of us discussed coupling classes to trademarks but IIRC never actually written up as part of the strawman.
Mark Miller wrote
Actually, it isn't another topic. It's why Kam's GilaMonster cannot see Monster's "health". "private" means "private to the class", not "private to the class and subclasses". "protected" would mean the second, and no, I don't think it's inevitable.
Aren't there also motivations related to covert channels especially given javascript's call and apply where any object should not have the ability to discern what private fields exist within an instance? This may be preventable using the meta attributes on property descriptors, but it seems like private should not 'leak' even to subclasses - per Brendan's earlier statements. I imagine Mark can or has written volumes on this, but probing a subclass of 'CreditCard' for the existence of private instance variables or static class declarations would seem to enable an attack vector and violate the class proposal objective of Strong encapsulation in order to support defensiveness and security.
In other words it's seems like uses cases related to private instance as well as private class (ie static private) are reasonable and at least as important as protected.
On Jun 8, 2011, at 8:50 AM, Mark S. Miller wrote:
On Wed, Jun 8, 2011 at 8:27 AM, Brendan Eich <brendan at mozilla.com> wrote:
Btw, in both C++ and Java, the semantics of "protected" is a mess, in different ways.
Sure. Let's not make those mistakes. Non-fixable in your view?
Not necessary in my view. There are all sorts of old constructs that address needs that turn out to be rare. Again, I've encountered an actual need for this only rarely, and only a mild need. As many people have said, language design largely consists of deciding to leave things out[1]. We should do more of that ;).
We can definitely leave protected out. My "seems inevitable" was in response to Kam bringing it up via a question that I expect will be frequently asked.
In C++ protected is used quite a bit, judging from Mozilla's codebase. But I see fewer uses in WebKit, so there may be some old inline avoidance (prehistoric C++ compiler issues) at work in the Mozilla case.
Many of the things we pulled from classes to make it simple enough to get accepted -- "requires"/"abstract", traits, earlier error checking, coupling to trademarks[2] -- are all more compelling that "protected".
Right, I'm not proposing adding protected, just discussing, doing A to Kam's Q ;-).
[1] Who said that first?
Niklaus Wirth, IIRC.
On Wed, Jun 8, 2011 at 9:55 AM, Brendan Eich <brendan at mozilla.com> wrote:
We can definitely leave protected out. My "seems inevitable" was in response to Kam bringing it up via a question that I expect will be frequently asked.
I really hope we can. My interest in adding class syntax to JS was that I saw the same patterns being done again and again in the code I worked with and I wanted a lighter more declarative syntax for them. The proposal we have does a great job of that.
So far, I very rarely see "protected" patterns in the code I work with, so adding that as language feature would buy me anything. It would just give me the ability to express something I don't care to express. Also, it we can get traits in JS.next.next, my hunch is that those two features won't play nice together.
In C++ protected is used quite a bit, judging from Mozilla's codebase.
I used to use it a lot in my C++ days too but I had a different mindset then. My C++ philosophy was "make sure no can break my class's contracts and encapsulation". When I moved over to JS I found that softening to "make it easy to not break the contracts and encapsulation but presume most of my users are not malicious". (The "private" stuff in the proposal that Mark is championing is great for when users actually are malicious.) From that angle, protected doesn't buy me much. It's not strong enough for security, and it's stronger than needed when we're all friends.
On Wed, Jun 8, 2011 at 11:22 AM, Bob Nystrom <rnystrom at google.com> wrote:
On Wed, Jun 8, 2011 at 9:55 AM, Brendan Eich <brendan at mozilla.com> wrote:
We can definitely leave protected out. My "seems inevitable" was in response to Kam bringing it up via a question that I expect will be frequently asked.
I really hope we can. My interest in adding class syntax to JS was that I saw the same patterns being done again and again in the code I worked with and I wanted a lighter more declarative syntax for them. The proposal we have does a great job of that.
So far, I very rarely see "protected" patterns in the code I work with, so adding that as language feature would buy me anything. It would just give me the ability to express something I don't care to express. Also, it we can get traits in JS.next.next, my hunch is that those two features won't play nice together.
In C++ protected is used quite a bit, judging from Mozilla's codebase.
I used to use it a lot in my C++ days too but I had a different mindset then. My C++ philosophy was "make sure no can break my class's contracts and encapsulation". When I moved over to JS I found that softening to "make it easy to not break the contracts and encapsulation but presume most of my users are not malicious". (The "private" stuff in the proposal that Mark is championing is great for when users actually are malicious.) From that angle, protected doesn't buy me much. It's not strong enough for security, and it's stronger than needed when we're all friends.
Well put!
On Jun 8, 2011, at 9:55 AM, Brendan Eich wrote:
On Jun 8, 2011, at 8:50 AM, Mark S. Miller wrote:
On Wed, Jun 8, 2011 at 8:27 AM, Brendan Eich <brendan at mozilla.com> wrote:
Btw, in both C++ and Java, the semantics of "protected" is a mess, in different ways.
Sure. Let's not make those mistakes. Non-fixable in your view?
Not necessary in my view. There are all sorts of old constructs that address needs that turn out to be rare. Again, I've encountered an actual need for this only rarely, and only a mild need. As many people have said, language design largely consists of deciding to leave things out[1]. We should do more of that ;).
We can definitely leave protected out. My "seems inevitable" was in response to Kam bringing it up via a question that I expect will be frequently asked.
In C++ protected is used quite a bit, judging from Mozilla's codebase. But I see fewer uses in WebKit, so there may be some old inline avoidance (prehistoric C++ compiler issues) at work in the Mozilla case.
Many of the things we pulled from classes to make it simple enough to get accepted -- "requires"/"abstract", traits, earlier error checking, coupling to trademarks[2] -- are all more compelling that "protected".
Actually, I disagree with this last assertion. Protected, if done correctly, is a very important aspect of a good class design that includes inheritance.
In a dynamic language such as JavaScript inheritance does not provide you subtyping guarantees. What it does provide you is a way to manage code reuse via implementation inheritance. Some people look upon this with disdain, others think it is one of the pillars of object-oriented programming. Whichever position you may take on that spectrum, the reality is that many application and framework designers will use ES class inheritance in this manner and that needs to be something that we factor into our design decisions (given that ES subclassing isn't subtyping it may well be the most important use case to factor into our design).
One of the reasons that some frown on implementation inheritance, is that without some language support there are lots of ways that in can break down if both the original class designer and the subclass designer aren't careful. A great overview of these issues is the OOPSLA'92 paper by Kiczales and Lamping www2.parc.com/csl/groups/sda/publications/papers/Kiczales-OOPSLA92/for-web.pdf
One of their main points is that the extensibility interface that a class exposes to subclasses should be treated as a distinct interface from the interface that the classes exposes to external clients.
A concrete technique for this is the use of the template method pattern (for example, www.oodesign.com/template-method-pattern.html ) which is one of the GOOF patterns. In this pattern, an abstract algorithm is represented as template method with explicit this method calls at each extension point. Subclasses are expected to over-ride does methods as a means of configuring the abstract algorithm.
protected if implemented with a reasonable semantics is a way to support such reuse patterns. For example, protected provides a way to formalize the subclass extensibility contract. It is the protected methods that define that contract. Similarly, the this call-backs in a template method really define such a reusability contract and should be specified in terms of protected methods.
We are on a well trod path here. protected is an important paving stone.
On Wed, Jun 8, 2011 at 11:50 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
One of their main points is that the extensibility interface that a class exposes to subclasses should be treated as a distinct interface from the interface that the classes exposes to external clients.
This is an excellent insight. When I'm in a static language, I definitely like making these two explicitly different. (I tend to go farther and make a distinction between the interface a class exposes to its subclass and the interface it uses for its own methods, which leads to me making most protected methods abstract or with empty bodies.)
In Javascript, I typically make the distinction through documentation or naming convention. A simple // protected goes a long way if you trust your coworkers. If you want to make it a little more explicit, JS gives us enough fun toys to do that. Where in Java I'd do:
class FacePainter { // Interface to outside world: public void paint(int x, int y) { drawCircle(x, y, 40); // face drawCircle(x + 10, y + 10, 10, 10); // eye drawCircle(x + 30, y + 10, 10, 10); // eye drawArc(x + 10, y + 10, 20); // mouth }
// Interface to subclass: protected abstract void drawCircle(int x, int y, int radius); protected abstract void drawArc(int x, int y, int radius); }
class CanvasFacePainter extends FacePainter { protected void drawCircle(int x, int y, int radius) { // draw on canvas... }
protected abstract void drawArc(int x, int y, int radius) { // draw on canvas... } }
In Javascript, I can do:
class FacePainter { // Interface to outside world: paint(x, y) { this.painter_.drawCircle(x, y, 40); // face this.painter_.drawCircle(x + 10, y + 10, 10, 10); // eye this.painter_.drawCircle(x + 30, y + 10, 10, 10); // eye this.painter_.drawArc(x + 10, y + 10, 20); // mouth }
// Interface to subclass: constructor(painter) { this.painter_ = painter; } }
class CanvasFacePainter extends FacePainter { constructor({ drawCircle: function(x, y, radius) { // draw on canvas... }, drawArc: function(x, y, radius) // draw on canvas... } }) {} }
By stuffing all of the "protected" methods into a single field that the instance stores, they aren't a visible part of FacePainter's interface unless you go out of your way to get them through painter_. That should be enough of a velvet rope to make it clear you're wandering somewhere you're not supposed to go.
Patterns like this have the advantage of supporting more than two interfaces. Where public/protected lets you define two windows, "to everyone" and "to my subclass", using composition to form those interfaces lets you define more fine-grained ones like "to the rendering system" or "to the UI layer".
On Jun 8, 2011, at 12:57 PM, Bob Nystrom <rnystrom at google.com> wrote:.
Patterns like this have the advantage of supporting more than two interfaces. Where public/protected lets you define two windows, "to everyone" and "to my subclass", using composition to form those interfaces lets you define more fine-grained ones like "to the rendering system" or "to the UI layer".
Backbone and others equip objects with capabilities added at runtime. Effectively traits, they retrofit existing objects with new behaviors that can only be done in a dynamic language. Currently new methods added via extend cannot take advantage of any closure defined within the constructor, though it seems that private enables this. These 'dynamic' patterns are becoming more popular in recent frameworks. I think these types of patterns are important and they have no analogue within static languages. I thought Allen had a great summary of this www.mail-archive.com/[email protected]/msg06566.html
Are static members inherited? What happens in the last line of the following code?
Based on my understanding of what the desugared code would be, the last line above would be an error because Dragon.allMonsters is undefined. That is, static members are not inherited at all which does not seem very class-like.
Peter