Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

A meeting halfway (alternative syntax proposal) #107

Closed
glen-84 opened this issue Jun 20, 2018 · 63 comments
Closed

A meeting halfway (alternative syntax proposal) #107

glen-84 opened this issue Jun 20, 2018 · 63 comments

Comments

@glen-84
Copy link

glen-84 commented Jun 20, 2018

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 (->).

class Example {
    a = 1;
    b = 2;

    hidden a = 1;
    hidden b = 2;

    constructor(a, b) {
        this->a = a;
        this->b = b;
    }

    example() {
        this->example();
    }

    hidden example() {
        // ...
    }
}

Why?

  • Looks (mostly) like JavaScript (subjective)
  • . refers to properties, -> refers to hidden members
  • "Field" and method declaration are consistent
  • Should work well with other keywords like static, const/readonly, etc.
  • Includes features from this proposal, like initializers, public/private, etc.
  • Less confusion about not having dynamic access?
    • this.#x – "why doesn't this["#x"] work"
    • this->x – No dot-access, so less likely to try this["x"]
      • this->["x"] could be supported if it's technically possible (now or later)
  • Includes hidden methods as well, but this could be added later.
  • An optional 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

  • Why not . for access?
  • What about access without this?
    • Something like ->x on its own probably wouldn't look good. Using this is also more explicit in distinguishing between fields and local variables.
@ljharb
Copy link
Member

ljharb commented Jun 20, 2018

  • "looks like javascript" is subjective, and I personally find that it looks like PHP - the current proposal looks much more like javascript to me. I think this bullet point should simply be removed.
  • Why is hidden here better than private?
  • this means that i'm using hidden on declarations, but not on access. this discontinuity does not exist in the current proposal, and contradicts your "Field" and method declaration are consistent point.
  • private methods are part of the current proposal, and are needed now, any alternative would have to cover those use cases.

@zenparsing
Copy link
Member

Thanks @glen-84 !

Let's start by taking a look at the downsides to this proposal:

  1. It consumes an additional binary operator, meaning that -> can't be used for other purposes in the future. The -> operator is one of the more pleasing operators that we have left. We don't see many requests to use -> for lambdas anymore, but it has been proposed recently for pattern matching.
  2. It may be difficult for users to understand or remember when to use -> versus when to use ..
  3. If users accidentally use . instead of ->, they will not get a compile-time error and may not even get a runtime error.

My thoughts in response:

  1. Losing -> is hard, but nowhere near as hard as losing #. We don't want to use thin-arrow for functiony (or blocky!) forms, so it seems OK to use it for something very different. C++'s precedence helps, and the visual implication of the arrow feeding the LHS into the RHS feels intuitive.
  2. There's a very simple rule: "if it's hidden, use an arrow!". I imagine that users will be able to internalize this rule fairly easily, but we need to gather feedback to know for sure.
  3. This is the hard one. TypeScript would certainly catch any of these errors, but what does the situation look like for pure JS? Any modern non-trivial code base (especially one that is doing hard encapsulation) will require a linter. Could a linter help here? Probably. I imagine that we could write a rule to warn the user if they are using dot-lookup where the property name corresponds to a in-scope hidden identifier name. Again, we need more experience to know for sure.

And some upsides:

  • It does not take #, one of the two remaining ASCII characters left to us, and in a way that many users find distasteful.
  • The community, in general, has expressed their preference for contextual keywords over sigils.
  • It makes it abundantly clear that hidden field access has completely different semantics than dot-lookup semantics.

@zenparsing
Copy link
Member

"looks like javascript" is subjective, and I personally find that it looks like PHP - the current proposal looks much more like javascript to me. I think this bullet point should simply be removed.

I say leave it. Perspectives are important.

Why is hidden here better than private?

Because of TypeScript's influence, private already means something very different. It means "static type-system compiler checked" private, not "hard" encapsulation private. I think both meanings are important, and we should start calling this "hard" thing something different. "Hidden field" seems fine to me. 😉

This means that i'm using hidden on declarations, but not on access.

Yep, tradeoffs. The price of having the same "thing" on declarations and access (i.e. #) is pretty darn high, though.

private methods are part of the current proposal, and are needed now, any alternative would have to cover those use cases.

I agree. I think there are some hidden methods in the example, but the "could be added later" phrase might be confusing things.

@ljharb
Copy link
Member

ljharb commented Jun 20, 2018

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 # to be high.

@jridgewell
Copy link
Member

I think I'm ok with everything here, until we get to the hidden modifier and accidental access:

hidden modifier

hidden sucks as a modifier keyword. Everyone wants it to be private, but I think typescript has screwed us here.

If users accidentally use . instead of ->, they will not get a compile-time error and may not even get a runtime error.

We think having consistent # usage will lead to less bugs, especially with the precedent typescript set. In TS, you declare private x then access with this.x. We know that will not work with hard private, but the average dev won't. Beyond that, Java, C, etc uses normal . access for anything declared private.

I 100% believe that devs will declare a private x and then access it with . and never realize they've written a bug.

@zenparsing
Copy link
Member

I don't find the price of # to be high.

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.

@glen-84
Copy link
Author

glen-84 commented Jun 21, 2018

@ljharb,

"looks like javascript" is subjective, and I personally find that it looks like PHP - the current proposal looks much more like javascript to me. I think this bullet point should simply be removed.

  • Did you conveniently leave out the (mostly)? 😄
    • Declaration with a keyword (think var/let/const etc.) is very much JS-like
    • Access would use a period if it was "possible". Thin arrows aren't ideal, but this.#name isn't exactly JavaScript-y.

Why is hidden here better than private?

  • As @zenparsing already mentioned, TypeScript (and if I'm not mistaken, PHP and C# as well?) use the private keyword for soft privacy – reflection is available.
    • Imagine private like a toilet cubicle – when occupied, you really shouldn't enter, but there are ways around that ("in an emergency").
    • Imagine hidden like an invisible toilet cubicle – you'll never be able to access it because you have no idea where it is in the first place
      • Please note that the exact keyword chosen is secondary to actually using a keyword instead of a sigil. It's open to alternatives, but private may not be an option.
      • Whether it's var or slot or field, is less relevant.
        • In fact, referring to the type of "storage" as opposed to the "visibility" might even be a better idea, as it's less likely to result in accidental attempts at dot access. i.e. Given the keyword slot (just an example, don't get caught up on it), the thin arrow then refers to a slot as opposed to a property. (I apologize if slot is not the correct term)

this means that i'm using hidden on declarations, but not on access. this discontinuity does not exist in the current proposal, and contradicts your "Field" and method declaration are consistent point.

  • You should compare it with existing JavaScript (or TS) [assume public fields exist], not to the existing proposal.
    • A public field declared as x; is accessed with this.x;. Ignoring the optional semi-colon, the only thing consistent is the variable name x. You don't declare it like this.x;. There is no period in the declaration, so why should there be a thin arrow there?
  • There is no contradiction – both field and method declarations use the keyword (hidden or otherwise).

private methods are part of the current proposal, and are needed now, any alternative would have to cover those use cases.

  • The README literally states that "Methods and accessors are defined in a follow-on proposal" (and similar text)
  • As mentioned, the example in the OP includes a hidden method. My comment about adding it later is simply to reduce the scope of the proposal, just as you have done in this repository.

@glen-84
Copy link
Author

glen-84 commented Jun 21, 2018

@jridgewell,

hidden sucks as a modifier keyword. Everyone wants it to be private, but I think typescript has screwed us here.

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.

@glen-84
Copy link
Author

glen-84 commented Jun 21, 2018

The problem with something like slot is that it may not make sense for methods ...

class Example {
    slot var1;
    
    slot getVar1() {
        return this->var1;
    }
}

Perhaps there is a keyword other than hidden that makes sense in both cases?

@zenparsing
Copy link
Member

zenparsing commented Jun 21, 2018

class C { 🚽 a = 1; }

@zenparsing
Copy link
Member

I must admit that I've never quite imagined an invisible toilet cubicle before. 😆

@glen-84
Copy link
Author

glen-84 commented Jun 21, 2018

I was never very good with metaphors. 💩

@bakkot
Copy link
Contributor

bakkot commented Jun 21, 2018

Personally

  • I dislike the combination of keyword declaration and non-dot access. JS already has four declaration keywords in class bodies: static, async, get, set. (Java has I think a full dozen.) None of these, nor any field declaration keyword in any language I can think of off the top of my head, changes the syntax for accessing the field (though it may change the object which holds it). This would. That's bad. It maybe would be OK if JS didn't allow you to use dot access anyway and just do something different with it, but of course it does.

  • I don't like further overloading the meaning of thin arrows for field access. C++ and PHP already use it for two different kinds of field access. I would not like us to introduce a third.

  • On the purely aesthetic question of what looks more like JS, this.#foo looks a lot more like JS to me than does this->foo.

@lloydjatkinson
Copy link

I agree with @bakkot

I previously commented on another issue about how out of place # is compared to actual keywords, but I think it's been deleted.

@glen-84
Copy link
Author

glen-84 commented Jun 22, 2018

@lloydjatkinson You think that the hash is out of place, but you agree with @bakkot that it looks more like JavaScript?

@littledan
Copy link
Member

but I think it's been deleted.

I haven't deleted any issues, but maybe it was closed. Did you check the closed issues?

@pumano
Copy link

pumano commented Jun 25, 2018

  1. no hidden please
  2. no slot please
  3. bring private to javascript please.

We have static, we can use private, public and etc. It's proper way to do it.

@rdking
Copy link

rdking commented Jun 25, 2018

@bakkot About your opinions...

  • I understand and agree with your dislike of private x without this.x. That's how it's typically done. The only problem preventing us from doing that is the requirement that private members are undetectable. I still have difficulty swallowing that as a requirement. The concept of a "hard private" only requires that a private member be inaccessible from outside the class. Going so far as to also be undetectable is pointless in the face of most ES code being directly readable. Even if it were possible to detect such fields, what harm could come of it as long as it remains inaccessible? Being able to see a locked safe doesn't make the contents of the safe any less... well... safe! Also, the argument about detectable private members interfering with public members of the same name in subclasses is a non-sequitur. ES's prototyping system already adequately handles this for public members already private members would be no different. An where the argument about developers attaching their own properties to an instance is concerned, since I can already mark properties as non-configurable, and private members should be that by default. I don't see the argument there either.
  • Thin arrows? Agreed. Don't do that. Could you imagine having to read code like this:
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.

  • About .#: it may 'look a lot more like JavaScript' than -> does, but it's far worse technically. The admitted access style limitations of the notation, the limited use inclusion of # as a valid variable name character, the heavy-handed alterations to the spec to allow for such an oddity, these are all very non-JavaScript like.

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.

@littledan
Copy link
Member

littledan commented Jun 25, 2018

This is an interesting idea, but I share the concerns that @zenparsing and @bakkot raised above.

In addition to the issues mentioned above, .# lends itself to shorthand without ASI hazards in a way that -> does not, as @bakkot explained
in the FAQ.

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
why I didn't think that this issue merited syntax changes, but if there is still concern about this aspect, I'm not sure if the syntax proposed here would meet that goal.

@rdking
Copy link

rdking commented Jun 25, 2018

@glen-84
I don't get hidden the way you're meaning it. If I saw that in a class, I'd expect a member with descriptor having enumerable: false, not a private property. As for 'slot' as a keyword: just why would you want developers to have that much of an understanding of the internals of an engine? I'm not saying it's a bad thing. I advocate such understandings in those I mentor. I'm asking why make such a detail that may not be true to the implementation apparent to the developer? Not a reasonable keyword. And what does it imply? [].length exists in a slot and it's public. So nothing about slot leads me to understand it as private.

@ljharb
Copy link
Member

ljharb commented Jun 25, 2018

@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.

@jkrems
Copy link

jkrems commented Jun 25, 2018

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';
  }
}

@glen-84
Copy link
Author

glen-84 commented Jun 25, 2018

@pumano,

If private is used, lots of users will expect dot access. Also, TypeScript would have to provide an option to switch between soft (don't emit the JS private keyword) and hard (emit the JS private keyword) privacy.

@glen-84
Copy link
Author

glen-84 commented Jun 25, 2018

@rdking,

I don't get hidden the way you're meaning it. If I saw that in a class, I'd expect a member with descriptor having enumerable: false, not a private property.

Fair enough. That's also solved by using a different keyword though.

As for 'slot' as a keyword: just why would you want developers to have that much of an understanding of the internals of an engine? I'm not saying it's a bad thing. I advocate such understandings in those I mentor. I'm asking why make such a detail that may not be true to the implementation apparent to the developer? Not a reasonable keyword.

just an example, don't get caught up on it + I apologize if slot is not the correct term

i.e. What I'm looking for is:

  1. A keyword
  2. Ideally doesn't refer to some form of "visibility" (so hidden is not completely ideal)
  3. Describes the type of data or declaration (like const or let)
  4. Makes it more obvious that it's not a property (or regular method) and that you should not treat it like one
  5. Ideally, works equally well for methods

... but perhaps that's close to impossible.

@glen-84
Copy link
Author

glen-84 commented Jun 25, 2018

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")

@pumano
Copy link

pumano commented Jun 25, 2018

@glen-84 looks like TypeScript crew do best they can! And they don't want to use some # syntax. Also they provide access via dot and why we can't add some property to class private property and throw access error, when try to access private property of class? I think if private keyword of property exist, dot access should check boolean flag about property access and if private - throw error. If it breaks backward compatibility, no problem. Default property are public and no problem.

Imho usage of # it all about making cosmetic feature on language level. Need fix root cause of problem in language, and not to add cosmetic solution for some people.

@glen-84
Copy link
Author

glen-84 commented Jun 25, 2018

I think if private keyword of property exist, dot access should check boolean flag about property access and if private - throw error.

I believe that's covered here.

@rdking
Copy link

rdking commented Jun 25, 2018

@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 private should have little if nothing to do with how ES might decide to use it. Even if the use of private were allowed under the current proposal, that would mean nothing to TypeScript unless the developers of that language decide to migrate to using the new ES usage. Even then, they could always place that behind a compiler switch. The keyword private really is the right one for the job. As for the issue of wanting to use this.x for private x, while I understand the urge, I also understand the reasoning for wanting to avoid this.x. As I said above, I do agree with the idea of hiding private fields. I just believe there are several better ways to achieve this without doing so much damage to the language structure.

@ljharb
Copy link
Member

ljharb commented Jun 25, 2018

@rdking again, if i can write a hasPrivateField() function for any class, then it becomes a breaking change to add the first private field, or to remove the last one. This isn't acceptable - the job includes making the existence just as private as the contents. Note that this is the same as "the existence of closed-over variables in a function must be private" - otherwise adding or removing variables to a function would be a breaking change.

@loganfsmyth
Copy link

[] access to fields

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.

class Foo
  #data = {};
  read(prop){
    return this.#data[prop];
  }
}

calculated field names in declarations (ES6)

Similar to above, when would you reasonably need to dynamically define the name of something private?

self-consistent meanings for symbols (is # a keyword or a name character? It shouldn't be both!)

To me, it seems like "a flag to mark something as private-related" is a very clear definition of what # does in the current proposal.

object construction symmetry between functions and classes (no way to directly add private members to objects even though all objects will have a private slot)

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.

To make matters worse, language tools like Proxy will break in the presence of private fields given the currently proposed spec.

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.

@glen-84
Copy link
Author

glen-84 commented Jun 27, 2018

@rdking,

How TypeScript uses private should have little if nothing to do with how ES might decide to use it. Even if the use of private were allowed under the current proposal, that would mean nothing to TypeScript unless the developers of that language decide to migrate to using the new ES usage. Even then, they could always place that behind a compiler switch. The keyword private really is the right one for the job.

TypeScript is a superset of JavaScript, so they would have to emit the private keyword by default. That means that compiling existing TS to ESnext would change its behaviour (from soft to hard private). Maybe that's okay, maybe it's not, but I still think it has to be considered.

As for the issue of wanting to use this.x for private x, while I understand the urge, I also understand the reasoning for wanting to avoid this.x.

Having private x and access without this.x is probably asking for trouble, given how this combination is used in other languages (and JS itself if you imagine an implicit public in front of public fields).

As I said above, I do agree with the idea of hiding private fields. I just believe there are several better ways to achieve this without doing so much damage to the language structure.

Which syntax are you in favour of?

The # doesn't tell you anything about the variable being declared. Nothing in the history of ES/JavaScript/JScript/CoffeeScript/TypeScript/(any other ES-based language) even hints at the notion of a variable or object member being declared in this way. Variables and object members are declared as words with word modifiers

💯

@glen-84
Copy link
Author

glen-84 commented Jun 27, 2018

@zenparsing,

have you tried writing classes with this syntax? What are your experiences?

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 (this.#var.another).

... and magically have the language know that when I write this._c I mean the private access thing. Is that what other people are generally feeling?

Not really ... I want it to "magically" know that in this.c, c is public/private/protected/static/whatever. I wish that we could just live with the runtime overhead TBH (although no one has ever quoted numbers). I never really understood the details.

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.

@rdking
Copy link

rdking commented Jun 27, 2018

@loganfsmyth

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.

Normally, I'd agree with you, but there's something that you can do with [] that is otherwise just an exercise in needless repetition without it: copy an object. With the spec in it's current state, classes with a large collection of private fields will be difficult to manage with out doing something like this.#container.field for every field access. That's just counterproductive.

Similar to above, when would you reasonably need to dynamically define the name of something private?

Ask anyone who's ever built a class library that uses factories to generate class definitions.

To me, it seems like "a flag to mark something as private-related" is a very clear definition of what # does in the current proposal.

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.

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.

For me, the want has been there since ES5. That predates both WeakMaps and classes.

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.

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.

@loganfsmyth
Copy link

Normally, I'd agree with you, but there's something that you can do with [] that is otherwise just an exercise in needless repetition without it: copy an object.

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.

Ask anyone who's ever built a class library that uses factories to generate class definitions.

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.

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.

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 #foo = 4 in the declaration and this.#foo as usage to be very consistent as a single form of usage that is easy to teach to users.

For me, the want has been there since ES5. That predates both WeakMaps and classes.

Fair enough. I could see a world where something like

var obj = {
  #data: data,
  method(){
    return this.#data;
  }
};

was viable, but it's not clear why that would need to be a blocker for implementing that behavior of 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.

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.

@doodadjs
Copy link

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 ?

Example

@loganfsmyth
Copy link

@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.

@doodadjs
Copy link

@loganfsmyth

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 frustation ? @ljharb is always against me and downvoting me, whatever I think.

and get no useful feedback

You just don't want feedback. Do you want ?

through a 10k line file

If you let it load, you'll get scrolled to the correct lines (1942-1954).

@loganfsmyth
Copy link

What frustation ?

"For the last time" starts off that post on quite a frustrated tone.

@ljharb is always against me and downvoting me

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.

You just don't want feedback. Do you want ?

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.

If you let it load, you'll get scrolled to the correct lines (1942-1954).

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.

@doodadjs
Copy link

@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.

@rdking
Copy link

rdking commented Jun 28, 2018

@glen-84

TypeScript is a superset of JavaScript, so they would have to emit the private keyword by default. That means that compiling existing TS to ESnext would change its behaviour (from soft to hard private). Maybe that's okay, maybe it's not, but I still think it has to be considered.

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 private is the ability to claim that it is a superset of JavaScript. Nothing would change for TypeScript users. Even ignoring all of that, why should JavaScript be restricted in its growth because of how a different language that tries to maintain compatibility with it uses certain words?

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.

Having private x and access without this.x is probably asking for trouble, given how this combination is used in other languages (and JS itself if you imagine an implicit public in front of public fields).

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 this.#field syntax, they would also get used to having to use such an awkward syntax when the declaration is private field. No matter what, something awkward is going to be introduced because of the requirements.

Which syntax are you in favour of?

Frankly, for reason's I've explained before, if the requirements can't be altered, then I'd rather the syntax was this#.field, where # would have the concise meaning "access the object's private container/slot" and would be required to be followed by an access operator (except in member declarations where the access is implied). Not likely to see that happen though since it was flatly refused. You should go look at #104. I solved several of the things that are broken about the private fields proposal without significantly altering anything in the FAQ. I'd still rather change the requirements and have private x with this.x where x would be non-configurable and marked private in the descriptor, but there are actually serious technical issues surrounding doing that which would make coding the scenarios for this into a JS engine without breaking something else rather difficult.

@mbrowne
Copy link

mbrowne commented Jun 29, 2018

@glen-84
As @rdking said:

I don't get hidden the way you're meaning it. If I saw that in a class, I'd expect a member with descriptor having enumerable: false, not a private property.

That would be my first reaction to 'hidden' as well.

I don't get why people are so allergic to the # notation. I doubt it would have been anyone's first choice, but given the technical constraints it's an elegant solution. How many years are we going to spend debating more and more alternative proposals, knowing that the ideal simplicity of private x and this.x is simply impractical for JS? If you set aside any purely aesthetic preference for -> over #, then I think the discussion in this has shown that there's no big advantage of -> and it introduces other issues of its own.

@syg
Copy link

syg commented Jun 29, 2018

Let me attempt to clarify some of the distaste around # being used to declare variables that's being brought up in this thread.

As people have said, it is true that field declarations with # names has no precedent in the JS and its family of languages. But that is by design: it is a novel thing to enable encapsulation.

To address the the lack of parallel with declaring variables with var or let or so forth: the semantics of # names and fields are not binding semantics. A binding form like let x creates a new lexically scoped binding in the current scope named x. I can use x as bare identifier in any context and it'll always have the semantics of "look up on the scope chain to see if there's a binding named x". If there's no binding on the scope chain, it might error or be undefined, depending on the language mode.

Using a # name in a class statement or expression does something else. A form like #prop creates a new private name when evaluated in the current scope named #prop, as well as declare a field named #prop. Because each evaluation creates distinct private names, the semantics feel very different. I cannot use #prop in any context: when doing obj.#prop, it's not just getting a private field named #prop, it's getting a private field keyed by the particular evaluation resulting from #prop in that scope. To make things concrete:

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, # names are a fundamentally new thing in the language. I think it makes good sense to me to have the # sigil mirrored both in declaration and access because of its novel semantics: there're new semantics both on the declaration side and on the access side.

@rdking
Copy link

rdking commented Jun 29, 2018

@mbrowne

I don't get why people are so allergic to the # notation.

I've mentioned this several times, but I'll enumerate my reasoning here: it

  1. unnecessarily breaks symmetry between objects and class instances
  2. breaks [] notation, limiting the number of available usage paradigms
  3. gives # 3 distinct meanings, 2 of which are always in use concurrently
  4. does not fit with any of the conventional standards for declaring members
  5. introduces an invalid IdentifierName character into IdentifierNames for only 1 very narrow set of use cases.

@glen-84
Copy link
Author

glen-84 commented Jun 29, 2018

@rdking,

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.

I'm pretty sure that that won't happen. In fact, it's part of the language's design goals.

The only thing that TypeScript would lose by continuing to provide it's own meaning for private is the ability to claim that it is a superset of JavaScript. Nothing would change for TypeScript users.

Not true. A user coming from ESnext must not be surprised by private x; not being hard-private when using TypeScript. "Introduce behaviour that is likely to surprise users" is a non-goal of TypeScript.

Even ignoring all of that, why should JavaScript be restricted in its growth because of how a different language that tries to maintain compatibility with it uses certain words?

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. 🍪

However, I am of the opinion that just as the proponents of the existing proposal believe that people will "get used to" the awkward this.#field syntax, they would also get used to having to use such an awkward syntax when the declaration is private field

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.

No matter what, something awkward is going to be introduced because of the requirements.

Probably. It's a shame.

You should go look at #104.

I did ... I don't like anything that involves #. 😄

I'd still rather change the requirements and have private x with this.x where x would be non-configurable and marked private in the descriptor

Yep, this is how I thought it would be solved, but apparently there are unmeasured performance implications.

@glen-84
Copy link
Author

glen-84 commented Jun 29, 2018

@mbrowne,

I don't get why people are so allergic to the # notation.

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.

If you set aside any purely aesthetic preference for -> over #, then I think the discussion in this has shown that there's no big advantage of -> and it introduces other issues of its own.

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.

@glen-84
Copy link
Author

glen-84 commented Jun 29, 2018

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 glen-84 closed this as completed Jun 29, 2018
@bakkot
Copy link
Contributor

bakkot commented Jun 29, 2018

@glen-84, to be clear,

I'd still rather change the requirements and have private x with this.x where x would be non-configurable and marked private in the descriptor

Yep, this is how I thought it would be solved, but apparently there are unmeasured performance implications.

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.

@glen-84
Copy link
Author

glen-84 commented Jun 29, 2018

@bakkot I should probably have added (etc.). Honestly I don't even understand why that would throw, and I'd prefer not to waste any more of your time trying to explain it to me.

@rdking
Copy link

rdking commented Jun 29, 2018

@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.

@bakkot
Copy link
Contributor

bakkot commented Jun 29, 2018

@rdking:

this same construct will throw (compiler error) in C++ and in Java as well

No, it won't. C++, Java. This is mentioned in the FAQ.

@jkrems
Copy link

jkrems commented Jun 29, 2018

@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());
  }
}

@littledan
Copy link
Member

@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?

@zenparsing
Copy link
Member

@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.

@rdking
Copy link

rdking commented Jul 2, 2018

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++ virtual or Java @Override). Will this also be implemented?

@hax
Copy link
Member

hax commented Jul 6, 2018

@glen-84

For those reasons (and others), I'm going to close this issue.

If your only concern is the symbol ->, would you like to consider other choices like :: (we discussed it in the classes-1.1 proposal: zenparsing/js-classes-1.1#19 and zenparsing/js-classes-1.1#38)?

The key point here is whether we use a separate new operator for private member access, or reuse . (which many think js devs would be confused) plus sigil (which many dislike, even hate in # case). If you prefer a separate new operator, we could leave out the concrete choice of symbol later.

Here is the possible symbol list (copy from zenparsing/js-classes-1.1#19 (comment))

. + sigil:

operator:

  • this->x C/C++/PHP style
  • this~>x Variant of ->
  • this::x understand as "scope operator", @zenparsing alternative
  • this..x @allenwb 's old double dot style
  • this~x Reuse the forgotten token ~ as a binary operator

@glen-84
Copy link
Author

glen-84 commented Jul 15, 2018

@hax,

I have considered a number of other characters, but honestly my preference remains at private + . for access (which I understand is not possible).

The this.@x syntax would probably get confusing when mixed with decorators, and most of the other options don't look great when mixed with public access, and could probably be used for other things (bind operator, ranges, math, etc.).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests