-
Notifications
You must be signed in to change notification settings - Fork 37
Yet another approach to a more JS-like syntax #28
Comments
We've been a bit hesitant to go for proposals like this, as it could be seen as a confusing extension to lexical scope. See zenparsing/js-classes-1.1#18 for related discussion.
Similarly, we've been hesitant to add further structures which implicitly reference
In this case, what would be the value of One concern I have about this proposal is that, if you forget to reference things through |
IMO, it's no more confusing than the fact that other syntax inside a class definition works differently than outside of it. And for beginners, it's probably less confusing than a closure. Anyway, it is exactly what we most commonly do now with factory functions when we want to have the equivalent of private fields.
I don't understand this concern. It's exactly the same as with other things that already exist. How is private.foo() more confusing than super.foo()?
Yes. It's not a function call, it's a construct. And it wouldn't be the only one that looks vaguely like a function call, but actually isn't. We don't expect |
But you don't actually do that in this proposal. |
I've added a clarification above. |
Another thought - the idea that private and public fields should not be in the same namespace (which I follow in this proposal) isn't necessarily wrong, but it does make a very common use case (change a field from public to private or vice versa) a menial and error-prone task, unlike in other languages where it consists of just adding or removing the private keyword in the class definition. |
I was pleasantly surprised about this other way proposed by @zocky, I hope it will get a lot of attention, as it's a really clever and clean way of solving this issue. |
I might agree with avoiding the implicit way of using private members, but for the other way, this is, the explicit one, using On the other hand, the fact it shares a lot of similarities with how And last but not least, it translates 1 to 1 with a possible "private" function that receives an instance and returns the previously stored "private" properties in a WeakMap, which makes this way super easy to teach and understand as it can be constructed with other existent primitives almost directly. I'm talking about the following example which could be easily refactored using this new syntax just by removing all the helpers, the constructor (it would be super nice to allow initializers too 🙂), and renaming
|
The FAQ is intended to address this proposal, which has come up a couple of times. As it says, the main thing for me is that |
So @bakkot you find |
Is this a technical objection, or just a matter of taste? Edit: |
As far as I can see, none of the items expressed in the FAQ explores the way expressed here, and in the other hand and IMHO if at the time of designing the way |
FTR: I've just shared this issue in JS Classes 1.1 Proposal as IMHO it addresses in a clean and understandable way the main topics that made that other proposal to emerge. |
@mgtitimoli, @zocky, to be specific: I think any proposal which allows class A {
private field;
constructor() {
this.field = 0;
}
} to create an object with a public I think this is a very important constraint, which is why it is the first question in the FAQ.
I don't really understand the distinction. My concern is not that I find it personally distasteful but rather that I find it misleading, if that's what you're asking. |
@mgtitimoli, right, that is the problem. |
@bakkot Just for you to know, they are currently evaluating using |
@bakkot - So basically, your objection is that somebody could accidentally introduce a logical error in their program? I struggle to see how that's a strong objection. Especially since the current proposal includes things like |
That proposal notably does not use I think "this feature does not make it significantly more likely programmers will introduce subtle logic errors" is one of the single most important constraints of language design. I know not everyone shares this opinion, but I think it's something I and the rest of TC39 are mostly pretty set on, and you are not super likely to persuade us to give it up. |
@bakkot - All I'm saying is that the assumption that it's significantly likely to introduce errors doesn't seem to be backed with anything except an assertion in the FAQ. In any case, it's a matter of pros and cons. This proposal allows all the functionality that's covered by the other syntax and does it in a clearly defined and unambiguous manner. In addition,
The one con seems to be the possibility that programmers coming from other languages might misunderstand it. |
And for the record, I would personally prefer |
Another point that might be brought up against the use of a That said the simplicity of this alternative looks both easy to learn, teach, and most of all look at, – and given it has been a reserved keyword in strict mode for a while i just hope that this is not held back solely by the precedence of languages that have differing heuristics with regards to the |
The main point IMHO this other way introduces is the alternative to use something different from this in the receiver part of the call, and this hasn't been discussed before. Ok, there are issues with private, that's fine, let's use |
And the other advantage is that with just one keyword we could do both: define what's private and access it. |
Food for thought, with plenty of caveats: Yet another possibility would be to use class A {
var field;
constructor() {
field = 0;
}
equals (other) {
return field == var(other).field
}
} To provide expected behaviour in other contexts, this could work also for other variables that are theoretically in lexical scope, but are hidden: function foo() {
var a = 1;
function bar() {
var a = 2;
console.log(a, var(foo).a) // outputs 2,1
}
} I fully realize that this is impractical and possibly inconsistent, because it's hard to polyfill and because the function name can be hidden as well, and because the function name doesn't really refer to the closure but rather to the function itself. But it might help us think. |
@zocky |
Let's not get sidetracked with this |
What would it take to present this alternative as a formal proposal? |
@thysultan You'd have to convince a TC39 delegate to champion it. Given the renewed consensus in favor of |
A nested class is part of outer class's code; I would be surprised if it were forbidden to access the outer class's private fields. But if it shadows those field by declaring its own, then referring to that name should be an attempt to access its own private field rather than the private field of the outer class. By analogy: function A {
let a = 'A';
function B() {
console.log(a);
}
B(); // prints 'a'
} function A {
let a = 'A';
function B() {
let a = 'B';
console.log(a);
}
B(); // prints 'b'
} This is exactly the behavior I expect, yes. |
@bdistin |
Is this a valid syntax? class A {
private a=1
}
const a=new A()
class B{
m1(){
return private.a
}
static m2(){
return private.a
}
}
new B().m1.apply(a) //1?
B.m2.apply(a) //1?
private(a).a //1? |
@maple3142 |
All of this is a bit hazy and somewhat conceptually incompatible with a prototype-oriented language like javascript. But that this is equally problematic with any approach to private fields in javascript, including the variation with '#' sigils. Despite proposing this particular syntactic solution myself, I'm leaning towards the conclusion that the idea of having a rock-solid data protection of private fields with javascript classes is conceptually wrong. There's too much prototype, apply, etc. baggage in javascript "class-like" logic. People who want that kind of thing should probably start a new language that compiles directly to WASM and doesn't allow prototype walking or applying functions to random objects at all. |
The disagreement about what the scoping should be in this thread (with multiple interpretations that are considered clearly correct) seems to point to the difficulty of using |
I disagree @littledan , it's just as confusing an unexpected with And that example is misleading @bakkot as the following is true: If changing the edit: I will go a step further. If it weren't for discussing private in human terms, rather than # Sigil, we wouldn't have identified what I can only describe as a spec failure of private. Logically and objectively only the left or right should be correct on the screenshot I posted last night. While I still believe that the left is a breach of hard-encapsulation (because you are getting private members of a class that isn't itself) and since hard-encapsulation is the main goal of private in javascript the left should be incorrect, I recognize that's my opinion. I am open to the idea that the left could be the correct result, but if the left is correct, that means the right cannot be. |
@bdistin if you wish for that to happen, you will need to tell the compiler which of class A {
#a = '1';
init() {
class B extends class A {
#a = '2';
log () {
console.log(this.#a)
}
}
new B().log()
}
}
new A().init() If you wish the inner class can access outer class's private property regardless of whether it is shaded. how should we tell the engine to do in this case? If you are in some strong typed language, you just can just case It isn't impossible to do that, but the current syntax(both #a and private.a) does not have enough information for the engine to do it correctly(TypeScript may do, but we are not talking about them. (they not only reject to do that to access outer class's private property prevent confusing, they also error out if the inner and outer class have the same private field name.) |
Sorry clicked close by accident. |
@mmis1000 There is no ambiguity in your example, the console logged will be '2'. But if you call new B().log.call(this) the console should log '1', not an error, just as if you didn't declare the #a in class B. edit: That is assuming accessing containing class private is correct. |
Incorrect, this will throw. Declaring |
If you are going with |
@bdistin I though, the way of symbol lookup is a different issue than this issue for. |
I apologize to the OP for going so off topic. I have created a new issue for the separate issue with Javascript's specification of private fields. |
I think @bdistin 's idea is still related to this ( The confusion is coming from the But This is why proposal like classes 1.1 suggest use a totally different operator for private state access ( But even we still use I feel it's much easy to teach and learn |
Actually, this can be very easily clarified, by stating that |
Best argument against using the # has to be the precedent if you wanted to add more access modifiers in future. What if we want both private and protected class methods. Or internal (I'm borrowing from C# access modifiers here) Or allow multiple access modifiers "private protected" or "protected internal"? To go with the current proposal will you have If not then how would we explain to new JS developers that some access modifiers are keywords (static, maybe others in future) and some are this new strange prefix syntax? |
Thanks for all of your comments on this thread. In the end, TC39 is moving ahead with the proposal described in this repository, which remains at Stage 3 after a long year of reconsidering various alternatives. See the README for more details. |
Really appreciate the work done by tc39, but it ultimately feels like you're saying that you've taken a year to pretty much disregard all the strong opposition and alternatives put forward to the syntax of this proposal from the development community. Saying you considered them but ultimately decided your way was the best doesn't feel like enough of a technical explanation to me. Is there anywhere we can see more of the additional discussion (other than here)? In a language that has heavily favoured semantically clear keywords in the past - import, export, class, static, break, continue, etc. etc. this seems inconsistent. |
@simonbuerger The issues on this repo, on proposal-class-fields, and on proposal-private-fields contain most of the discussion (check closed issues especially). The private syntax FAQ gives a high-level summary of a lot of our thinking, and tc39/proposal-private-fields#14 in particular is a long thread discussing alternatives and their feasibility or lack thereof in great detail. |
@bakkot thank you for taking the time to share that with me for the context. I can see the thorough debate that went on. I guess it may take me a good long while to get past the general "ickyness" of the whole |
As someone coming from a strong html and CSS background, semantics of a language/language feature is the most important consideration, and quite frankly # carries no semantic meaning whatsoever. Ok, I'm done. |
Static members are properties of the constructor... This is completely valid and already works in Chrome today. class Example {
static foo = 'test';
static bar() {
return this.foo;
}
baz() {
return Example.foo;
}
}
console.log(Example.bar()); // test
console.log((new Example()).baz()); // test I don't understand why we can't just use the keyword Maybe I'm missing something but I don't see what's wrong with the following. class Example {
private _foo = 'test';
bar() {
return this._foo;
}
}
const example = new Example();
console.log(example._foo); // undefined
console.log(example.bar()); // test We should also have the |
See the FAQ. |
So the following would be impossible to add to the language? class Foo {
private test = 'test';
}
class Bar extends Foo {
// creating a public member `test` that masks the private member `test` in class Foo
test = 'bar';
} class Point {
private x = 0;
private y = 0;
equals(point) {
// accessing private members from another instance of the same class.
return this.x === point.x && this.y === point.y;
}
} I would say these are nice to haves but I wouldn't be that upset without them. For example, you could just do the following. class Foo {
private _test = 'test';
}
class Bar extends Foo {
// no long conflicts with private member `_test` in class Foo
test = 'bar';
} class Point {
private _x = 0;
private _y = 0;
get x() {
return this._x;
}
get y() {
return this._y;
}
equals(point) {
// no longer an issue
return this.x === point.x && this.y === point.y;
}
} |
Uh oh!
There was an error while loading. Please reload this page.
I realize I might be beating a dead horse, but it seems to me that I'm far from being the only one who intensely dislikes the use of # for private fields/methods. So here goes:
private
keyword to introduce private field, methods, setters, getters, exactly as withstatic
.private.name
to explicitly access the private fields of this object, if there are conflicting names, just like we do withsuper
. Also useprivate["name"]
for non-ident and calculated field names.private(otherObject).name
orprivate(otherObject)["name"]
to access the private fields of another object of the same class. For symmetry,private(this).x
is the same thing asprivate(this)["x"]
private.x
orprivate["x"]
or justx
if there's no variablex
in scope.private
andprivate(otherObject)
are not valid constructs without being followed by.field
or["field"]
and throw syntax errors.AFAICT, this achieves all the requirements, without introducing a wildly asymmetrical new syntax for private fields, AND gives us a natural way of reading code in human language, solving the naming problem that was brought up on other threads.
The text was updated successfully, but these errors were encountered: