-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to critique this proposal #32
Comments
I definitely feel the complexity budget squeeze as the language continues growing. But... I guess I really don't share the impression that this proposal uses less of it than the existing one. It adds four new and diverse syntactical forms, one of which overloads an existing one with radically different semantics: I agree that the important thing is to consider proposals holistically, with a particular emphasis on overall complexity. I am just confused by the implication that this proposal is less complex that the current one. To me it feels far more so. |
As @allenwb states, the comparison should not be to any single proposal in isolation, but to the full set of class features that are either in some form of development or have been deferred to future proposals. That is a large list, with noticeable unresolved problems. We see our alternative as a small, closed, self-sufficient set of essential functionality. Of course it's not perfect (yet?), but that's not the point. The point is to see if we can create an alternative that is complete (and therefore represents less design risk), while avoiding some of the troubles that the current proposals find themselves in. |
One conflict here is that some of the use cases this "minimal" proposal doesn't address, are indeed essential. The complexity that the current fields proposals add tends to be deemed "very much worth it" by those who need these use cases. If a less complex proposal can address them, then great! However, if not, then it's not actually a better proposal - it's just a simpler one, which isn't inherently better in and of itself, despite the value of minimizing complexity. |
I guess it depends upon your definition of "essential". The definition I use is provided in #11 But from a Max-min design approach, the meaning of essential is fairly straight forward. If all of the stake holders in the design agree that something is essential, then it is essential. If there is disagreement as to whether or not some feature or capability is essential, then for the purposes of the Max-min design it can't be considered essential. As a starting point on what is essential, I this we have significant consensus that some mechanism that provides securely encapsulated per instance state is essential. |
Max-min motivated the ES2015 design, and while that may very well have been the right decision, I'm not sure that was in fact considered "successful" nor that max-min is automatically the right approach to take now that we have the post-ES6 process. |
This proposal is great! It avoid the main syntax issues in current proposals:
It also make the internal slot (instance variable) concept much much clear by introduce I agree public property initialization is good to have, but not must to have in current stage. We could leave it, check how TypeScript users use the features here proposed and property initialization they supported together, then decide whether or how to introduce public property initialization in the future. I LOVE this proposal! Thank you @zenparsing and @allenwb to bring this to us! |
Note that it's the combination of reusing the |
@littledan I think even having initializers it will make much less new ASI hazards than current proposals. class A {
var x = 0
*gen() {} // syntax error
var y = 1
[computed]() {} // syntax error, and it's not a new ASI hazard for semicolon-less coding style
} All other weird cases are just gone. |
This confuses me a bit; can you be more explicit about what you're referring to? |
|
@hax:
It's not a class local variable, since a function which closes over it will see different values depending on how the function is invoked, which breaks a fundamental contract of variables in JavaScript. It's not a variable at all, in any sense. Short of
I'm not saying any of these are necessarily bad, except the first. I'm just saying they collectively add a huge amount of complexity to the language. I stand by that claim. |
Of course this proposal adds complexity to the language. If it didn't add some complexity it would be adding any new expressibility to the language. What it tries to avoid in adding inessential complexity. You aren't arguing that this proposal adds more complexity then then union of all the class extensions proposal that are already in the TC39 pipeline, are you? |
I can't answer this without knowing which things we're considering to be class extensions proposals. The class fields proposal, sure, but... Callable constructors? Decorators? Mixins? A future shorthand for private field access? I don't see how this proposal would rule any of those out, for example, and commentary elsewhere suggests it's not intended to. But as such I don't understand why we'd be interested in comparing the complexity cost of "this proposal" vs "the current class fields proposal, plus callable constructors, plus decorators, plus mixins, plus shorthand". I do claim it adds more complexity that the current class fields proposal. I personally would be more than content to get just that proposal and no others, so from a max-min perspective, it seems like that proposal would be a strict improvement over this one. |
Let's look at a specific use case: self-hosting builtins with class definitions. The fields proposal is not sufficient in its current form to completely address that use case. It is missing the capability to (a) securely decompose methods and (b) expose access to encapsulated state outside of the class for use by "friends". If we want to take a holistic view of class features, then we must also take into account the additional proposals that would support these capabilities, along with any uncertainty or risk surrounding those features. |
(a) Sure. Then let's consider as our point of comparison the class fields proposal + the private methods proposal, which is incidentally the set of stage 3 proposals touching classes. (Although, of course, using per-instance private fields containing functions amounts to the same thing; it creates more objects, but provides the capability desired with nothing beyond the class fields proposal.) (b) This doesn't require language level support. let subscriptionClosed;
class Subscription {
#state;
static finalize() {
subscriptionClosed = s => s.#state = 'closed';
delete this.finalize;
}
}
Subscription.finalize(); I don't claim it's clean, just that it's doable, and concisely. Furthermore, if we got decorators it could be done more cleanly than this. I don't think this proposal can be considered in isolation from other proposals such as that one.
To my eye, it looks like it requires only the class fields proposal plus the private methods proposal. The union of those two still seems much simpler than this, while providing more. Do you think there are others which are necessary for this use case, or some other specific use case? Which? I'm sorry to push on these details so hard, but I really feel like we can't much talk about this without being explicit about what you and Allen mean by "the full set of class features that are either in some form of development or have been deferred to future proposals", and which of those you feel this proposal subsumes or renders unnecessary. As long as it's not "all proposals touching classes", which it seems it is not, then "the full set" isn't really specific enough for me to tell what you're intending to point at. |
Sorry I don't get this. Could you give a small example show the breaking contract?
Of coz it's not function variable, but I don't see why "instance variable" is wrong. We already use the term "instance variable" several years in many languages and loosely in JavaScript. For example DC's very old article: https://crockford.com/javascript/private.html already use "instance variable". |
I claim that the delete trick for exposing private access is not sufficient, in the sense that it is not usable enough on its own to leave it as-is. It creates a void into which some kind of syntax must find its way. If that syntax is decorators, then we must add the decorators proposal to the "complete" class feature set. Furthermore, there are some issues with those proposals. From a developer's point of view, does it make sense that you can have static fields and static methods but not static private? Why is a private field just a value slot, but a private method has property-descriptor-like semantics (e.g. allowing accessors)? Is exposing something like PrivateName through decorators the safest way to expose private access? Will developers get over their distaste for The list above isn't meant to be a point-by-point critique of the current set of proposals. Rather, it is meant to call attention to the fact that the current direction for classes seems to imply a fairly high degree of complexity and risk. Part of the problem is that the current approach to developing class features is so open-ended that it's hard to tell what classes will end up looking like, or what features will end up in them. Narrowing our scope helps give us confidence. Also, we understand the disruptive nature of this proposal, and I really appreciate the feedback! |
I agree that we shouldn't encourage anyone to use the delete trick. I think we should work on a static blocks proposal for this purpose. Several TC39 members are talking about this and I hope we'll see a Stage 1/2 proposal soon. |
Sure, let me elaborate. JavaScript functions are lexically scoped: the variable to which a given identifier refers is determined at the time the function is defined, not the time at which it's invoked. (It's a little more complicated than that because of dynamic scope like That means that if I have var x;
[...]
function f(){
[...] x [...]
} then no matter what I do with With this proposal's class A {
var x;
f(){
return this->x;
}
} This creates only a single function To be more explicit: var x = 0;
class A {
m(){
return x++;
}
}
(new A).m(); // 0
(new A).m(); // 1
// vs
class A {
var x;
constructor(){ this->x = 0; }
m(){
return this->x++;
}
}
(new A).m(); // 0
(new A).m(); // 0 The bit that confuses me here is that Allen himself has repeatedly proposed allowing variable declarations in class bodies as means of create normal variables in the scope of the class body, that is, variables which would be closed over by methods in the class body by the usual means and which have a single identity shared among all such methods and invocations thereof, including on different instances. That is after all how variables work in JavaScript.
Having tutored a few brand-new JS programmers in the last couple years who had encountered Crockford's article or teaching materials based on them, I can now say with high confidence that that particular use of the term "variable" is extremely confusing to new programmers, and in my experience pretty much always causes them to have the wrong mental model of what's happening not just in that code but in the rest of the language. |
I disagree. Like I say, I'd be fine with just the current fields proposal, or that + private methods. I wouldn't necessarily object to a more general construct which also satisfied that case; I just don't actually think that pattern is bad enough or use case common enough that syntactical support for something else is in fact inevitable. I think that pattern is plenty usable, especially if we're concerned about the overall complexity of the language. I don't especially like it, but I also really don't like adding more syntax, so...
Agreed that there are some issues and edge cases, though I think they're much less of a big deal than you seem to. But this thread was mainly about complexity, and I wanted to say that this proposal seems to me to be much more complex than those do. (Also, specifically, "why can't I have private static?" seems like fundamentally less of a complexity cost than, for example, "why does
That's why I keep asking for which specific proposals you intend this one to rule out. If it doesn't rule out callable constructors, or mixins, or decorators, or shorthand syntax for private field access, then it seems to me you have exactly the same problem: you just have the class fields and methods proposals, except with more syntax and no public fields, and no more or less of an ability to say whether any other feature will end up in classes. That is to say, I don't think narrowing our scope actually does us give any additional confidence unless we specifically say which things are now out of scope for any future proposal. Since discussion in other threads (e.g. #33) suggests that this proposal is not intended to "finish" classes, it really feels to me like this has not been done. Absent that, if anything I think narrowing our scope gives us confidence, since we are then not considering interactions with those things which are now not in scope. |
@bakkot Ok, I see. But I will argue, for the guys who know plain old JS class A {
var x;
f(){
return this->x;
}
} is just sugar of function A () {
var x;
this.f = function () {
return x;
}
} This is the closure private pattern from ES3 era. I understand some newcomers can't get it in the beginning, but when they finally learn the connection between class and function, they will not expect
Yeah, I still remember when I first saw "instance variable" in a C++ book 25 years ago, I was a little confused. But unfortunately, there are many "variable" term issues in programming languages, eg. global/static modifier VS normal variable in many languages are much more confused IMO. So I don't think "instance variable" is a big deal. 😝 |
BTW, @zenparsing I'm curious could we just use class A {
var x
f1() {
return x + 1
}
f2() {
const x = 2
return this->x + x // only need this->x when there is a shadow x
}
} |
But it is not just sugar of that. There's only one function object created by the class definition. And it's especially confused given static blocks: class A {
static {
console.log('reached');
}
var x;
f(){
return this->x;
}
} The static block runs just once at class definition time, but a new |
@bakkot After all, |
Max-min classes is an alternative to all of the following proposals: It is orthogonal to and does not replace: It does not address one way or another functionality address by: |
Yes I advocated for that as an alternatively way to remediate some of the issues with the private methods and static privates proposals. That experience is one of the things that convinced me a piecemeal or kick the ball down the field approach to extending class definitions was leading to too much overall complexity. It led me to agree to participate in developing this max-min proposal. In the context of the Max-min proposal I withdraw my support for class body lexical declaration . Your variable binding argumenbts seem predicated upon the keyword Instance variable declarations in our proposal do not create a lexical variable binding. They reserve an instance variable "slot" (if you will) in the per instance encapsulated state and introduce a lexically scoped (and namespaced) hidden name binding. The hidden name binding mechanism is essentially the same as used in the private fields proposal.
|
@allenwb, thanks for the list.
No; I'm mostly just worried about |
@erights I find "slot" intuitive. When the Chrome implementation of the private field proposal became available for experimentation, I tried it out by migrating a collection of custom elements that had been using WeakMap-based private state previously to see how it felt*. In my head "slot" was exactly how I was thinking about them — right down to the fact that, just like "slots" on DOM objects, methods referencing the 'slots' end up restricting what constitutes a compatible call context / receiver.
|
I don't agree term "variable" is bad, though I understand your concern. The truth is, even you use other name, like "field", "slot"... you have no chance to change the traditional usage of "instance variable", many people/books will still use it to denote whatever spec name it, from the past to the future.
I agree "instance variable" may confuse newcomers. But I don't agree it "can do long-lasting harm to their mental models". I, as a non-english speaker, we suffer much much more pains from term issues, not only from the original books/docs/articles... but also from translations. Many terms have multiple translated names, and some different terms just use a same translated name! Eg. "attribute" and "property" are normally both translated to same word "属性" in Chinese. The term chaos is very common problem in Chinese programmers' life, but there is no evidence it did long-lasting harm to Chinese programmers' mental models. When we communicate to each other, if we say "属性", most time we will find whether it denote DOM property or HTML attribute from the context. Sometime we will say "DOM属性" or "HTML属性" if we anticipate the confusion. I don't mean the preciseness of term is not important. If we Chinese programmers could have translated terms in more consistent forms , my life would be much much easier! But on the other side, we know the world is imperfect, and we can accommodate to it. The problem of "instance variable" vs "function variable" have no chance to worse than using same word to denote "property" and "attribute" IMO.
To me, the difference of var VS prop is :
I'm very glad class instance variable also very simple and very close to function variables in the old closure private patten, which do not involve prototype and descriptor. |
It's very hard to predict which term is more intuitive. But as a non-English speaker, I will give you a interesting measurement of a term: whether there is a conventional translated name for a term. In Chinese:
If we finally use I guess other non-English programmers could give similar observation. |
As we debate the meaning of "variable" in the context of JavaScript, let's not forget that "global variables" are also a thing. |
@hax Your comments about translation are every enlightening. We native English speaker language designers typically don't even think about the difficulty of translating our hair-splitting terminology distinctions for other languages and cultures. You should really writeup these observations about translation of programming concepts as a blog post or other standalone presentation. |
I may not be the most familiar with the technical details involved but from having read the existing proposals and compared to this proposal. The claim that this proposal reduces complexity when compared to the existing proposals seems simply untrue. Allow me to elaborate: 1. Currently in javascript anywhere that you can declare a variable of any kind you can initialise it. 2. Currently in javascript var has a specific meaning 3. Static blocks - these have no comparison in existing JS or the existing proposals |
@rhuanjl We are presenting our proposal as an alternative to the four proposals list in #32 (comment) . Our assertion is that our proposal is introduces less overall complexity then the totality of those four proposal. Any place we say less complex or reduced complexity we are implicit meaning compared to that other set of proposals. But, of course it does add complexity to the language. Every addition to a language adds complexity along some dimension. |
I'm aware it is intended as a comparison to the existing proposals and I was implicitly comparing it to them in the above comments. To make my points clearer:
As far as I can see that is the comparison for the features this proposal includes VS the existing proposals. And in my view my points above on the increase of complexity stand for these comparisons. The rest of the difference is that this proposal chooses to drop:
|
@rhuanjl |
Whenever we talk about "complexity" we have to be careful about what beans we are counting. From a language designer point of view, we like to look at the complexity of interactions between different elements of the language. So while it may be "simpler" to just say "use That said, your worry about the reuse of |
Is the primary reason complexity is a problem, though, because it makes language designers' life difficult? Or is it because it (eventually, perhaps) makes users' life difficult? |
For a shorthand proposal, see https://github.com/littledan/proposal-private-shorthand . |
@ljharb |
Can you please help me to understand what sort of "complexity" these initializers cause users? The only thing I can sort of think of is that you have to know that for
|
Extrapolate to larger classes with more kinds of expressions in computed property positions and in initializers. In read the code, what scope will you assume for each expression?What order of evaluation will be used? |
I had assumed, and hoped for in-order evaluation, so class C {
@observable x = 12;
@computed get xPlusOne(){ return this.x + 1 }
} would work fine, and was relieved to hear that the spec did indeed demanded top-to-bottom evaluation. As for scope, if you over-think it, then sure, the |
A wise person once told me that all successful software is doomed to turn into a ball of mud. JavaScript is an incredibly successful piece of software. We're just trying to put off the inevitable as long as we possibly can. Complexity tends to manifest in special cases. As an example, under the current set of proposals, it appears that instance private "fields" and methods are OK, but static private is not (because of the prototype/TypeError issue). That is a special case. Does it make sense to the user? Probably not. And yet we still want to be able to securely decompose class-side methods, so now we need to invent some other feature to fill in the gap created by that special case. This is the process by which software becomes muddy. |
@zenparsing I don't understand how this proposal and the class fields proposal differ in this aspect. This proposal also omits "static variables". |
First, since this proposal does not introduce the concept of "fields", it does not tempt the user into thinking (by analogy with other languages) that there should be such a thing as "static private fields". Second, it does provide support for class-side secure method decomposition via |
@zenparsing, even if we did successfully manage to convince everyone to call these "instance variables" (which again I do not expect to be possible or desirable), those almost always have a counterpart of "class variables", i.e., static fields. So I don't really see why people would be any less tempted. |
@bakkot I believe people will be less tempted because the usage of As I explained, |
function () {
class C {
x = 12;
[this.x](){ return this.x + 1 }
}
} What is the property name of the method defined above. |
The Big Ball of Mud Pattern http://laputan.org/mud/ |
function () {
class C {
x = 12;
[this.x](){ return this.x + 1 }
}
} To be fair, initializers do not contribute more confusion in this example. But the last example of As I understand, these examples demonstrate that if we want to introduce initializer, we should never allow lexical class X {
var foo = this.x // syntax error
var foo = () => this.x; // also syntax error
var foo = function () { return this.x; }; // ok, though not useful,
// because you can use hidden method instead
} Does it solve the concern of complexity and confusion of initializers? |
To elaborate on my claim of this proposal introducing extra complexity:
If the problem is that computed property names in class fields would cause confusion this does not feel like the answer - "make the syntax confusing for most people to avoid edge cases that will cause weird errors for a few" instead IMO if those edge cases are such a problem prohibit them specifically rather than making the whole syntax messier. Banning lexical this from any kind of class field (or equivalent)'s name seems eminently sensible to me and a far simpler fix. |
I think it would be helpful if we separated out the syntactic changes in this proposal from significant divergences in semantics. For example, this proposal shares the WeakMap perspective on private state (called "private fields" in the status quo proposal and "instance variables" in this repository) and other important semantic details of the status quo proposal. On the other hand, it changes the surface syntax of accessing private state from The authors of the proposal in this repository have concerns about the the surface syntax of the status quo proposal, as well as what they predict will be the likely mental model arising from that syntax. I think it would be helpful to separate out the syntactic changes from more significant semantic divergences (e.g. no initializers). |
Maybe not the right place to ask. @zenparsing @allenwb |
@hax Thanks for following up! Unfortunately, the committee (for the most part) hated it. See tc39/proposal-class-fields#107 for a more recent discussion of a similar alternative. That alternative also failed to get traction. I now wonder whether we are better off without any class-based private field syntax, at least for the time being. What do you think? |
@zenparsing I know this proposal is still pre-stage 0, but I assume the stagnate of the process is because of the “sunk cost” from current already stage-3 proposals. I happened to know one who attend the TC39 meeting in May, and he told me that he don't like current field proposals, (and even refuse to take the job of implement it in V8, as an active contributor of V8) but it's very hard to publicly show the support to this proposal, because it may be interpreted as the low evaluation of the work of his friends in tc39 who are pushing current proposals. I understand such situation of interpersonal relationships, it may be the problem of all the organization. (Someone in this thread may know whom I mentioned, please keep it secret because this is coming from a private conversation.) So I think we need more feedback from out of the TC39. This is why I think babel may help. As babel 7 will ship (And I already make the most classes-1.1 syntax parsable in my fork of babel. See hax/babel@a274086)
As I can say, private state per-instance is a rigid demand when using ES2015+ class. Private out of class is not a must have because we already have other facilities (local var, closure, module, symbol, etc.) This is why I am not very interested in https://github.com/tc39/proposal-static-class-features/blob/master/FOLLOWONS.md#private-names-declared-outside-of-classes or similar proposals. And, in some comments I'v pointed out the problem of public field in OO theory/practice, the solution is private state wrapped by accessor. Without per-instance private state, we are very likely slip to flawed public field solution which current proposal advocated and TS already shipped. That's why I hope we can continue the work of classes 1.1, which is the most hopeful alternative to current proposals as far as my knowledge. Note, if you are talking about just introducing private symbol (symbols which can not be reflected by But even private symbol was revived and I myself can accept it as the solution of private, I'm afraid the community may not be satisfied. We should land private symbol in ES2016, not ES202X!! The only chance of broad acceptance is provide syntax sugar for common usage like private state in class, then we go back to the mud of syntax 👻 . PS. @ljharb I notice you 👎 the comment of @zenparsing , would you give more details about your thought? Though we may have many disagreements in the discussion, your idea is always important to me. Thank you. PPS. To @zenparsing and all in TC39 (hope you can read it), Sometime I am disappointed about current status of TC39 { /*
*/ } but, it's the duty of TC39 to lead the evolving of the language. Even I believe myself may be as qualified as the member of TC39 if only consider the comprehension and experiences of the language and the community, the guys out of the committee like me eventually can only give the feedbacks and suggestions. You the members need to take the hard part. (Admitting we failed on a stage 3 proposal is definitely one of the hardest. ) And please be patient (to all the possible complains, misunderstandings, rants...). And don't give up. Thank you. |
@hax I'd really like to make sure all of these perspectives are taken into account, both yours and other people with feedback. I appreciate all the time you've been taking to work to guide TC39 on these features. If you can convince them to be open with their concerns, I'd really like to talk with anyone who has problems with the current class proposals. I think we need to think about how to make the committee more open to community feedback--GitHub often isn't the best medium for in-depth discussion, but at the same time it's definitely impractical to bring everyone into TC39 meetings (who would pay for all the free food for that many people?!?! 😄); I think we need some other way of managing discussions online and bringing people in. I totally agree with you that there are many people in the community who are just as qualified to help as the people who go to physical meetings. I'm pretty new to community organizing and outreach, so I'd be happy to hear any suggestions people have. I've been trying to address all the concerns that I can. Some concerns are inherently un-addressable and we have to make a decision--for example, some people continue to say that classes as well as encapsulation are bad ideas. I'm glad to hear that you see encapsulation in classes with ergonomic syntax as an important feature as I do. At some point, we just have to decide as a committee and community whether we want these things or not. Some concerns are things that we can work through, either through tweaks in the proposals (e.g., the ASI discussion) or adjusting the scope (e.g., the discussion about static class features). I'm trying to maintain an overall consistent, opinionated design philosophy with class features proposals, with a few core principles. The slogan I have in mind is something like, " |
When reading and raising issues about the Max-Min Classes 1.1 proposal it is important to understand how it relates to the current set of class feature proposal that are at various states in the TC39 process pipeline.
This proposal is a response to the overall complexity that would be introduced into the language by the full collection of current and contemplated proposals. Basically, we don't think that the JS complexity budget for the near future is big enough to invest so much of it into class extensions. JavaScript has generally followed a minimalist design style. From that perspective, the contributors of this proposals are primarily concerned about the amount of complexity that the existing proposal would inject into the language. The complexity concern has also been raised by a number of community members.
This proposal is not a point-by-point attack of any feature of any of the current proposals. There are many good ideas in the existing proposals. Some of them we've borrowed. Instead, this proposal is an attempt to fight back against overall complexity. We are not trying to "fix" or improve other any existing proposal. It is an experiment to see where we might end up if we start with a clean slate and took a minimalist approach to adding some pretty essential functionality that is not in ES6 class definitions. We want to see where we get if following that same Max-Min methodology that enabled the introduction of class definition syntax into ES6.
This proposal is a carefully balanced set of functionality that attempts to include just enough and not too much new capability and associated new complexity. The best way to critique this proposal or understand the rationale for our decisions is to focus on the actual proposal rather than trying to do a feature-by-features bake-off against other proposals. For example, rather than asking "why did you leave out public fields?" ask "Why didn't you include a mechanism for declaring instance own properties?"
We fully expect to have to explain difference between various features of this proposal and other proposals. Just remember, that sort of point-by-point comparison is not the design methodology we have followed and may not give you the best understanding of this proposal. Try to understand it as a whole before you start the feature point comparison.
The text was updated successfully, but these errors were encountered: