-
Notifications
You must be signed in to change notification settings - Fork 113
A meeting halfway (alternative syntax proposal) #107
Comments
|
Thanks @glen-84 ! Let's start by taking a look at the downsides to this proposal:
My thoughts in response:
And some upsides:
|
I say leave it. Perspectives are important.
Because of TypeScript's influence,
Yep, tradeoffs. The price of having the same "thing" on declarations and access (i.e.
I agree. I think there are some hidden methods in the example, but the "could be added later" phrase might be confusing things. |
I don't think TypeScript's definition carries more weight than the english definition of the word, and I don't think "hidden" conveys "hard private" whatsoever - hidden things can be found, private things can't be accessed. I don't find the price of |
I think I'm ok with everything here, until we get to the
We think having consistent I 100% believe that devs will declare a |
Let me clarify my meaning: we need to keep the language adaptable, or it won't survive in the long term (nothing does!). We don't know how we will need to grow the language in the future, and so the best we can do is to leave as much open space as possible. From that point of view, consuming one of the two remaining ASCII characters is a high price to pay for any language feature. |
|
Please refer to my previous post, specifically to the point about the keyword referring to a storage type vs visibility. I think if it was something like: slot myhiddenvar; ... there might be fewer attempts to access it with a period, as it's not just referring to a type of "visibility", but to an entirely new concept. Again, don't focus on the exact keyword, but the idea of referring to the "type" of field. I'm somewhat included in "everyone wants it to be private", but you're right, it's a bit awkward now because most users would then expect dot access. |
The problem with something like class Example {
slot var1;
slot getVar1() {
return this->var1;
}
} Perhaps there is a keyword other than |
class C { 🚽 a = 1; } |
I must admit that I've never quite imagined an invisible toilet cubicle before. 😆 |
I was never very good with metaphors. 💩 |
Personally
|
I agree with @bakkot I previously commented on another issue about how out of place |
@lloydjatkinson You think that the hash is out of place, but you agree with @bakkot that it looks more like JavaScript? |
I haven't deleted any issues, but maybe it was closed. Did you check the closed issues? |
We have |
@bakkot About your opinions...
class Example {
#handler
constructor() {
this->handler = ()=> { //do something }
}
} It's not entirely bad, but I'd rather not. ES is very close to C++ in terms of grammar already. Having some distinction from it is not a bad thing.
It's issues like the ones I've mentioned here that are the reason why, even with this proposal in so late a stage, that there are still more of us with dissenting opinion than there are members of the tc39 board. Like so many others that have posted in here, I'm hard-pressed to find even a single associate of mine who thinks of this proposal as a proper way of handling private fields in ES. This is supposed to be the public review phase for this proposal, and yet the public's distaste for this has been made clear due to all the counter proposals. I provided a proposal myself that removed most of these non-JavaScript like limitations (#104), yet it was denied by @littledan without even so much as an explanation of the reason or link back to where the issues may have already been discussed. I find myself concerned that such pithy handling of public opinion may lead to a lack of faith in the process, and eventually in the board itself. |
This is an interesting idea, but I share the concerns that @zenparsing and @bakkot raised above. In addition to the issues mentioned above, Anyway, if we get at the point where we're only debating surface syntax in these issues, I'd call that progress. Would everyone be satisfied with either this syntax or the other one, or are there further points of disagreement? For example, I've heard some questions about whether we should make this syntax consistent with a potential future private name declaration syntax; in the static class features repo, I explained |
@glen-84 |
@rdking being able to see a locked safe absolutely makes the contents less safe, just like leaving a closed purse visible inside a locked car makes it less safe - these metaphors probably aren’t particulsrly useful. If you can detect the existence of a private field, you can write your code to behave differently when the field is present or absent, even if you can’t get at the contents. Thus, adding or removing a private field would be a breaking change. The only utility this feature has is if both private fields’ existence and contents can not be detected. Separately, there’s no “public review” phase of proposals - at all stages, public feedback is invited and considered - alongside all other considerations. |
One note on the "safety" part: It's not just about protecting the class from its users, it's also about protecting the users from the class. E.g. a case that has been brought up in the past: class MyClass extends SomeBase {
constructor() {
// this shouldn't break just because a patch version of `SomeBase`
// decided to add a private field called "id"
this.id = '123';
}
} |
If |
Fair enough. That's also solved by using a different keyword though.
i.e. What I'm looking for is:
... but perhaps that's close to impossible. |
I wonder if it would make things more obvious if the declaration syntax was completely different, like: class Example {
private {
x = 123;
y = 456;
}
private getX() {
return this->x;
}
} (again, don't take it too literally – I just mean "something that doesn't look like a regular property declaration") |
@glen-84 looks like TypeScript crew do best they can! And they don't want to use some Imho usage of |
I believe that's covered here. |
@ljharb My point was that an item locked in a safe has a certain level of security provided by the safe that is independent of whether or not the safe itself is hidden. The level of security for the items in the safe is predicated on how difficult it is to violate the security measures of the safe itself. Hiding the safe itself is extra security that I don't see as necessary in the case of "private". The point of this argument is that if people can see the code, and see the private members, it doesn't matter if the private members can be detected in code or not. As long as the private members cannot be accessed, then the job is done. @jkrems That makes almost no sense in the face of how class extensions function. When you extend a class, you need to be aware that internal changes in the base class are going to affect your subclass. That is part and parcel of the nature of classes. If your boss slightly changes your job description, isn't there a chance you're going to need to learn something new? That is also why there's been a long standing push to favor encapsulation over extension. I understand (and even agree with) the desire to hide private members to this degree. I just don't believe that doing so is worth either the utter destruction of the symmetry that exists between classes and functions, or the heavy-handed truncation of the list of usable access methods. @glen-84 How TypeScript uses |
@rdking again, if i can write a |
Not once can I think of a time where I've wanted to dynamically look up a property of an object when it was a property that was conceptually private. Any time I've ever made a property private, it was on an object with a clear static shape. Personally I'd consider dynamic property access on anything that isn't a simply POJO, which class instances are not, to be an code smell and to be avoided. If you're doing dynamic lookup on private data, I'd expect that to 99% of the time be some dataset that you'd want to live on an object within a single property, e.g.
Similar to above, when would you reasonably need to dynamically define the name of something private?
To me, it seems like "a flag to mark something as private-related" is a very clear definition of what
Assuming I understand this point, I struggle to think of a time where I'd want private fields, but not be using class syntax. If I'm declaring an object using an object literal, you can usually already use scoping to define private-scoped behavior for that object. What is a case where dynamically adding a private to an existing object would be desirable? All the ones that come to mind are probably better served by using a WeakMap.
The assertion that this spec is adding something new that breaks proxies is not true. Proxys that will fail in the context of private fields can already fail in the context of several other existing features, private fields adds another case that can trigger failure, but it is far from the first to exist. |
TypeScript is a superset of JavaScript, so they would have to emit the
Having
Which syntax are you in favour of?
💯 |
I haven't really. Although having a quick look now, one thing that does bother me, is something like this: const val = this->var.another; i.e. switching from one access operator to another within the same expression (looks bad). I guess the current proposal is slightly better here, in that it at least includes the period in both cases (
Not really ... I want it to "magically" know that in Using an underscore instead of a hash would just make it look slighly less crappy, but having any sigil in the first place is the real unfortunate aspect. Since the main issue is related to access and not declaration, and thin arrows are not a major improvement (minor/subjective: easier to read/looks like an access operator), I'm considering closing this issue. Let me know what you think. |
Normally, I'd agree with you, but there's something that you can do with
Ask anyone who's ever built a class library that uses factories to generate class definitions.
In terms of specifications, that's terribly vague. The only thing in a programming language allowed to be anywhere near that vague is an operator.
For me, the want has been there since ES5. That predates both WeakMaps and classes.
I never asserted that this is a new problem. What I am asserting is that while WeakMaps provide a work-around for creating private fields without classes, having direct language support without a solution to that problem will only turn the problem into a major issue as adoption of private fields increases. |
Fair enough. You're certainly right that it is slightly more verbose in that one specific case, but at the same time, any time you add a new field to a class, you still need to think "should this be copied or not", so even if you iterate, you'd end up having to visit your copy function to potentially exclude the field. Not to mention that copying may include performing a deep copy and thus require custom code anyway.
That would require both dynamic naming and being able to define an undefined number of private fields, which seems to conflict with class syntax itself, since it expects explicit declarations of the fields that will exist. I guess that goes back to your point of dynamically creating private fields too, not just dynamically choosing the name.
My point there wasn't that it should be defined that way, just that the assertion "It shouldn't be both!" only applies if you divide it aggressively. I see
Fair enough. I could see a world where something like
was viable, but it's not clear why that would need to be a blocker for implementing that behavior of classes.
My primary disagreement here is that it asserts that this is a problem that private fields themselves are responsible for addressing. This problem already has a solution, which is for Proxy objects to properly pass through the correct objects, and that same solution fixes all cases where it is currently broken. It feels like everyone pushing for private fields to be responsible for fixing that wants to ignore that we would still leave several other cases where the proxies would still be broken. We'd have add a massive special case for private fields, while not actually addressing the overall issue. Proxies would still break with WeakMaps, and still break in any other way that depends on object identity. |
For the last time, why don't you implement "public", "protected", "private", "friend", ... as real scopes, where the runtime knows when it is allowed and when it is not allowed to access a field ? |
@doodadjs If you spell out what you're proposing, we can have a discussion, but venting your frustration without providing concrete points means there's no way address your comment in a constructive way, meaning you'll stay frustrated, and get no useful feedback. What does "real scopes" in this context mean? What actual runtime behavior are you expecting? How do you propose making private properties not step on eachother while associating them with object instances? No-one is going to spend time looking through a 10k line file to understand your point. |
What frustation ? @ljharb is always against me and downvoting me, whatever I think.
You just don't want feedback. Do you want ?
If you let it load, you'll get scrolled to the correct lines (1942-1954). |
"For the last time" starts off that post on quite a frustrated tone.
It's important to separate technical disagreements from personal ones. It is the job of everyone involved in this community to argue for the way they think things should go. There will always be disagreements, and I'm sure quite a few other people would disagree with you, Jordan is just very active because he cares about the language. Disagreements are to be expected.
Everyone here want feedback, absolutely. However, feedback does not always guarantee change, and it's important to not take that personally. There are always going to be times when things don't go the way you want, but feedback and discussion are still critical to exploration of any new feature. My frustration with your comment is that, while you clearly have an image in your head of how things should behave, it is not clear from your description what that is. Without that understanding, it is not possible to respond to your comment in a productive way. If this thread is to stay useful as an active discussion, it's important that there be clear points to respond to.
Absolutely, but you have a mental model of all of that in your head. You may well get tons of useful information from those 12 lines because of that, but for me as someone unfamiliar, I would need to spend a huge amount of time understanding that code and anything it calls out to before I could understand it. By posting the link without an description, what you are doing is asking each individual person on this thread to read and understand enough of your sample code to be able to review its merits. Realistically, very few people will have the time and energy to do that, which means that you'll have failed to get your point across. What I'm asking is that, if you do want to use doodad as a demonstration of how things behave, is take that into consideration and more clearly spell out your expectation for the behavior of class fields, rather than expecting other people to spend time reading through your library. |
@loganfsmyth Thanks a lot, I really love your reply. I was sure that it was enough (a code demo). Believe me or not... I have being working with other's code since about 10 years, without any documentation, .... and had to produce many things in light time. |
Just because TypeScript is a superset of JavaScript, doesn't mean it will remain that way. Just the fact that TypeScript has to be "compiled" means that the developers are free to alter the meaning of any of JavaScript's keywords. The only thing that TypeScript would lose by continuing to provide it's own meaning for Should the U.K. stop calling cookies "biscuits" because we've started using that word to mean a flaky, buttery bread? Or if you don't like that because their use of "biscuit" came first, what about "trainers"(UK) == "sneakers"(US). Need they stop using "trainers" for shoes because we use it for fitness experts? It's all English, and American English is (mostly) a superset. The two languages diverged as they evolved. Restricting JS because of TS would be tantamount to asking JS to stop evolving into the space occupied by TS.
I get what you're thinking here, and I agree... somewhat. JS already uses concepts from other languages with a slightly different implementation. What's more is that the nature of the current language, and the requirements for adding private fields does force the issue about changing the access notation for private members. However, I am of the opinion that just as the proponents of the existing proposal believe that people will "get used to" the awkward
Frankly, for reason's I've explained before, if the requirements can't be altered, then I'd rather the syntax was |
That would be my first reaction to 'hidden' as well. I don't get why people are so allergic to the |
Let me attempt to clarify some of the distaste around As people have said, it is true that field declarations with To address the the lack of parallel with declaring variables with Using a function makeEx17() {
return class Ex17 {
#privateField;
getField() { return this.#privateField; }
};
}
let ex17a = new makeEx17();
let ex17b = new makeEx17();
assertThrows(() => ex17a.getField.call(ex17b), TypeError);
assertThrows(() => ex17b.getField.call(ex17a), TypeError); So this is all to say, |
I've mentioned this several times, but I'll enumerate my reasoning here: it
|
I'm pretty sure that that won't happen. In fact, it's part of the language's design goals.
Not true. A user coming from ESnext must not be surprised by
I don't disagree, I just don't think that it's as simple as that. Regardless, this discussion is laregly pointless while the current proposal doesn't even use a keyword. 🍪
I don't agree. Mixing a clean declaration syntax (common in other languages) with the hash abomination would likely be a mistake. I'd prefer it if the declaration just remained the same.
Probably. It's a shame.
I did ... I don't like anything that involves
Yep, this is how I thought it would be solved, but apparently there are unmeasured performance implications. |
It just doesn't fit the language in my opinion (and many other's). It also looks like a comment character (and is in other languages). In addition, it doesn't really make room for other types of access/visibility (protected, etc.). @rdking added a few more items above.
I agree. It doesn't solve the real issue, which is not being able to access private state without a sigil. That's the unfortunate part. |
Thanks for all the feedback. As mentioned above, this proposal doesn't solve what I believe to be the real issue (sigil-based access), and the thin arrow can also look even worse when switching between private and public members. For those reasons (and others), I'm going to close this issue. |
@glen-84, to be clear,
The proposal you quoted here isn't viable because it means that class Base {
private id;
}
class Derived extends Base {
id;
}
new Derived; would throw, which seems bad. Performance issues aren't the (only) problem. |
@bakkot I should probably have added |
@bakkot Funny thing is that this same construct will throw (compiler error) in C++ and in Java as well. It's fairly common for it to not be possible to lower the privilege level of a private member. |
@rdking I don't think that's correct. The following is perfectly fine in Java: // javac Foo.java && java Foo
public class Foo {
private static class Base {
private String id = "base value";
public String getId() {
return id;
}
}
private static class Derived extends Base {
public String id = "some value";
}
public static void main(String[] args) {
Derived d = new Derived();
System.out.println(d.id + " / " + d.getId());
}
} |
@zenparsing It's hard for me to understand your comment #107 (comment) . Do you see this as a potentially viable alternative that we should be investigating further? Do you have any ideas about how to resolve the technical issues that the FAQ mentions about this proposal? |
@littledan When I tried this syntax out on some code, it just didn't "feel" quite right. My intuitions are not quite comfortable with it. Neither are my intuitions comfortable with I'm glad that @glen-84 brought up this alternative syntax but I'm personally not interested in pursuing it further. |
ok... I verified it for myself. I was wrong on both counts. It's sad I've let my knowledge about those languages become so outdated. I guess I've become a little too fascinated by what's already possible in JS. While testing, I noticed that base class members are hidden by subclass members with the same name, regardless of privilege level. I guess this is what is intended for the private fields proposal. The part that I found most interesting is that members functions of the base class will not access member fields of the derived class even if the derived class has fields of the same name (barring any use of C++ |
If your only concern is the symbol The key point here is whether we use a separate new operator for private member access, or reuse Here is the possible symbol list (copy from zenparsing/js-classes-1.1#19 (comment))
operator:
|
@hax, I have considered a number of other characters, but honestly my preference remains at The |
This is a syntax-only proposal, that mixes some of the syntax from JavaScript Classes 1.1 with the current (and more featureful) proposal in this repository.
Declaration of hidden ("private") members is accomplished with the
hidden
keyword, and access utilizes the thin arrow (->
).Why?
.
refers to properties,->
refers to hidden membersthis.#x
– "why doesn'tthis["#x"]
work"this->x
– No dot-access, so less likely to trythis["x"]
this->["x"]
could be supported if it's technically possible (now or later)public
keyword could be added to public fields for symmetry and explicitness, similar to TypeScript. However, it has been suggested that we avoid discussing this addition initially.Q and A
.
for access?this
?->x
on its own probably wouldn't look good. Usingthis
is also more explicit in distinguishing between fields and local variables.The text was updated successfully, but these errors were encountered: