Closed babakness closed 6 years ago
Please read the FAQ; you'll hopefully see why no keyword is feasible.
As for your personal taste; "ugly" is subjective, and many don't find it ugly.
Ok, subjectivity aside, # could have great potential for something in the future. Its a very assessable character. Right there above the middle finger.
This is an edge case concern. I can appreciate the need to have truly private attributes. Why not ### instead of # given the far reaching concerns and utility a single # can offer.
Consider, for example, that # in Scala is used for accessing nested classes. My point is that it is important to really consider the future of the language the ergonomics that are being forfeited. The FAQ is also full of opinions, for example, it is stated that this
is confusing. I disagree, it is merely an implicit parameter rather than explicit as it is in python (self
). Furthermore this
is Javascript, it's a necessary learning curve.
I have to admit that having a this.bar
inside of class
and not a function is confusing in that prototypes are kind of masquerading as classes. Here, this
isn't an implicit parameter, so yeah, its confusing.
This need for #
is suspicious however. It feel that there is this supposed pressing need for the world that a keyword is just going to really slow things down. Why? Couldn't it be argued that a keyword could become # behind the scenes? So therefore # could really be anything, ###, or whatever.
A keyword is plausible. It is clearer, easier to read, easier to teach, and better to look than a misplaced #
tattoo.
A keyword used for declaration that is not also used for access won’t work (per the FAQ).
Do you have another single-character syntactic suggestion besides #
? Multiple chars would just make this important feature (way more important than accessing nested classes, for example) less ergonomic.
Imagine if async/await
was paired with symbols instead. Let's say #%
and #$
; so that one might write #%*function() { const foo = #$ bar() }
or if the setters and getters were done using bizarre symbolic contraptions.
Of course keywords are better. A keyword for generators would have been even better. What follows is actual Perl code.
($l=join("",<>))=~s/.*\n/index($`,$&)>=$[||print$&/ge;
Let's stop making Javascript a write-only language. Adding these kinds of symbols is damaging to be sure. Perl went down this "developer ergonomics" rabbit hole. The aftermath of Perl attracted many to following wisdom
https://www.python.org/dev/peps/pep-0020/
#
as proposed here is not easy to read, not easy to explain, not explicit, and inconsistent. In fact the problem the FAQ mentions about this
are because it is implicit rather than explicit. The implicit behavior of this
makes it difficult to teach. It is therefore ironic that #
is suggested instead.
class Foo {
const private bar = 5
let private baz = 6
method() {
console.log( private bar)
private baz = 7
// private bar = 8 throws exception (const)
}
}
What's wrong with that?
It doesn’t allow you to access private values on other objects besides this
.
I’d really suggest rereading the FAQ; all of your reasoning is sound, but seems to be missing some of the constraints.
I see this in the FAQ
this is already a source of enough confusion in JS; we'd prefer not to make it worse. Also, it's a major refactoring hazard: it would be surprising if this.x had different semantics from const thiz = this; thiz.x.
This also wouldn't allow accessing fields of objects other than this.
Those words are there just hanging around, no examples. Please explain and amend the FAQ to state the issue clearly with code so that one can reason about what exactly is the problem. Thanks.
Here's an incredibly necessary and common example:
class Array {
#arrayData = [];
static isArray(obj) {
if (!obj) { return; }
try {
return !!obj.#arrayData;
} catch {
return false;
}
}
}
Here's another:
let i;
class Foo {
#id = i++;
compare(other) {
return other && this.#id === other.#id;
}
}
function getFooClass () {
let internalParameter = 'bookkeeping'
let totalMembers = 0
let prototype
const Foo = function Foo() {
prototype.totalMembers = ++totalMembers
Object.defineProperty(this, 'totalMembers', {
get: function() { return totalMembers },
set: function() { throw 'read only' }
});
}
prototype = Foo.prototype
return Foo
}
const Foo = getFooClass()
const a = new Foo()
const b = new Foo()
a.totalMembers
// 2
b.totalMembers
// 2
Foo.prototype.totalMembers
// 2
a.totalMembers = 3
// read only
Is this roughly what you are communicating? Please clarify.
Note that internalParameter
is not accessible at all from the outside and totalMembers
is available from the outside to read only, but it can be changed from the inside. Also the entire thing about making Foo.prototype.totalMembers accessible can also be removed, obviously, so its just
function getFooClass () {
let internalParameter = 'bookkeeping'
let totalMembers = 0
let prototype
class Foo {
constructor(){
++totalMembers
Object.defineProperty(this, 'totalMembers', {
get: function() { return totalMembers },
set: function() { throw 'read only' }
});
}
}
prototype = Foo.prototype
return Foo
}
const Foo = getFooClass()
a = new Foo()
b = new Foo()
... or it can be made available to Foo but read only. Or not at all readable. Or we can return from getFooClass both the Foo class (prototype
really) and a utility object that gives us stats, etc.
Your "totalMembers" example is what would be a static private; my examples use a static method to read instance private fields off of multiple objects.
The above with the additional constraint that it would not be intended as an API... is that correct? I'm asking so I can clarify the problem to myself
I'm not sure what you mean by "intended as an API"?
OK, I better understand now, thanks--I'll think it over for a while because there needs to be something better than wasting #
for this purpose... not everyone even likes the class
pattern let alone wants forfeit other potential utility of the symbol in the future.
Seriously if we are at a point where all other symbols are exhausted, extinguishing the last of the symbols from this endangered species is unwise. At least the #
should be used where all programming styles would benefit. Consider for example the proposed |>
operator.
https://github.com/tc39/proposal-pipeline-operator
Its great and all breed of programmers benefit from it. From different skill levels to different philosophies. And that's two characters while far more useful for day-to-day programming.
For now however, shouldn't there be a let
or const
declared to state if this can or can't be re-assigned? Seems more consistent.
It’s a field, not a variable.
Here you go
const privateStuff = {}
const initPrivateStuff = function(instance) {
privateStuff[instance.id] = {
data: []
}
}
class Foo {
static isFoo(obj){
if(obj && obj.id){
return privateStuff[obj.id] != null ? true : false
} else {
return false
}
}
constructor(){
this.id = Symbol()
initPrivateStuff(this)
}
}
const foo = new Foo()
const bar = new Foo()
console.log(Foo.isFoo(bar))
//true
console.log(Foo.isFoo(foo))
//true
If I understand the issue correctly, please consider if your objections are more valuable than forfeiting #
from scenarios that better benefit the community along with all other sound objections again #
(lack of readability, inconsistency with the actual language, teachability, cryptic, etc.)
(To be clear: #
would be unacceptable for me in pipeline even if nothing else was using it)
And yes, i think instance private is so massively important that even if it killed every future syntax proposal forever, it would be worth it. Just to be clear. (I personally find # here quite readable; there’s nothing like this in the language to maintain consistency with; i think it’s trivially teachable; etc)
(To be clear: # would be unacceptable for me in pipeline even if nothing else was using it)
Disagree, but # would be a stupid syntax for that too.
And yes, i think instance private is so massively important that even if it killed every future syntax proposal forever, it would be worth it.
Given the remark about the future, fervor here is so high as to be unreasonable and pertinaciously obstinate.
Just to be clear. (I personally find # here quite readable; there’s nothing like this in the language to maintain consistency with; i think it’s trivially teachable; etc)
Strongly disagree.
What problems does # solve here not easily address by my (possible more portable) example? That said it is valid to make the language more ergonomic with measure cost benefits.
I personally feel JavaScript should grow its functional abilities. Its fine for classes to get better too, why not? People who don't like it can not use it. Done.
But I a strongly disagree that this pattern is so important as to deserve the only remaining symbol. No way.
https://stackoverflow.com/questions/2078978/functional-programming-vs-object-oriented-programming
https://blog.learningtree.com/functional-programming-object-orientated-programming/
#
Not for an offshoot feature in the OO pattern some might choose to avoid. No.
Using the example provided above
class Array {
#arrayData = [];
#example = 'test'
static isArray(obj) {
if (!obj) { return; }
try {
return !!obj.#arrayData;
} catch {
return false;
}
}
method() {
console.log(#arrayData)
// mutation
#arrayData.push(' :-( ')
#example = 'something else'
}
}
Let's say there is some function private
available within functions/class instantiated by new
or Object.create
that is a more elaborate production of the initPrivateStuff
above. Private is essentially inside an implicit closure and has access to a specific instances private fields.
From here we have the syntax sugar such that private
as a keyword de-sugars into the funciton
class Array {
private arrayData = [] // de-sugars to private(this, 'arrayData', [])
private example = 'test' // de-sugars to private(this, 'example', 'test')
static isArray(obj) {
if (!obj) { return; }
try {
return !! private(obj,'arrayData') // no sugar, however, for sugar `this` can be bound to something else
} catch {
return false;
}
}
method() {
console.log(private arrayData) // de-sugars to private( this,'arrayData' )
private arrayData.push(' :-D ') // de-sugars to private( this,'arrayData' ).push(' :-D ')
private example = 'something else' // de-sugars to private( this,'example', 'something else' )
}
}
Or something along these lines... private
is not available where private is already defined. Just like anything new in ES6. For example, Array.prototype.includes
could already be overwritten by some library, and that's fine. There are versions of old JavaScript where super
is not a reserved keyword.
Wouldn't this address the aforementioned concerns? It is also explicit and readable using words rather than any strange syntax using #
.
In other words, #
is replaced by a function private
which has syntax sugar to be expressed using keyword where appropriate.
Added benefits to using private
and the private
keyword:
// magic-library.js
export const external = function(privateFn){
privateFn(this, ... cool stuff.. )
... // more cool stuff
return whatever
}
// foo.js
import {external} from 'magic-library'
class Foo { method() { return external.call(this,private) } }
2. Readability. The keyword `private` is pretty self explanatory.
3. Future proof. JavaScript doesn't sacrifice `#` for this philosophy of OO
4. Teachable. Understanding `this` is important in JS. The concept of a `private` function and they sugar around `private` is as easy to understand once you understand why the `super` is there only within a constructor. Likewise `private` is there for classes and you can reproduce the behavior with `prototype` as aforementioned.
@babakness Your opinion against OO has nothing to do with that proposal. And FP is surely not the only way you go to develop a full computer software. But, like you, I'm on favor to use a keyword for the private scope instead of "#".
@doodadjs You're misreading. I use OO as well. However, anyone who would benefit from a better use of #
has an opinion made relevant to this proposal by its use of #
. Anyway, I read from your comment that you like OO a lot and still don't like #
as used here. Good! 🤙
I'm offering help in straightening this out, so please first review this and see if it covers the use case where this proposal wants to take away #
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361021019
If so, see how this alternate syntax might be suited or offer insight in making it work.
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361033139
What do think?
I feel that some of those who want #
used here are not trying to constructively solve the issue and just want to derail everything to get #
used for this purpose. I appreciate your constructive help.
It’s not “taking away” a token, it’s using one. “There might be a proposal in the future” is not a good reason to hamstring an existing proposal.
Do you have a concrete suggestion for a better use of #?
@babakness, @ljharb I really prefer the following structure :
class Foo {
private user = 'doodadjs', // secret
private password= 'doodadjs2018', // secret
getList() {
return window.fetch(`https://api.mycomain.com/foo/list?user=${this.user}&password=${this.password}`);
}
}
const o = new Foo();
console.log(o.user); // access denied
console.log(o.password); // access denied
But because "o" is just an object with "proto" set to Foo.prototype, I'm thinking that it should still be possible to do :
o.user = 'mario';
o.password = 'mariobros';
To prevent this, may I suggest that the author of 'Foo' should do something like :
class Foo {
@writable(false), @configurable(false)
private user = 'doodadjs', // secret
@writable(false), @configurable(false)
private password= 'doodadjs2018', // secret
getList() {
return window.fetch(`https://api.mycomain.com/foo/list?user=${this.user}&password=${this.password}`);
}
}
@ljharb Do I have a concrete suggestion for # today, no. Does this proposal have a good use for it? No.
Yes it is perfectly valid to not allow JS to be disqualified from the benefits of having a highly accessible #
in place.
I've made a suggestion above, you not address that. You're not being constructive. There is this fixation on using#
, not sure why. What's wrong with the suggestion above? private
makes so much more sense and I've even suggested a way of accessing private field on other objects.
@doodadjs In case I missed it, how do you access private fields on other objects? See @ljharb example and my suggestion as well.
@babakness Private fields are private to the methods in their definition(1). I did an implementation of PRIVATE in doodad-js.
(1) "definition" being Foo and Foo.prototype combined.
(Sorry if I use the wrong terms)
EDIT: No... (1) should be : the class definition, sorry
@babakness re: private arrayData
for access: variations on this syntax have been raised and rejected several times before, including at least once or twice in the main syntax discussion thread and I think once or twice in other random threads. I don't have it in me to track them all down right now, but among other concerns, it doesn't play well with nested property access (e.g. what's the equivalent of this.#next.#data.type
?).
To be clear, we've discussed this and alternative syntaxes at extreme length over several years, and the more general discussion of private fields in JS goes back to at least 1999. We are aware that some members of the community find this syntax distasteful, but we can't constantly keep rehashing the same variations on the same several dozen alternative syntax proposals.
@bakkot That's not just distasteful for me, but has a non-sense... Why scoping with special characters like '#' instead of a keyword like 'private'. That's weird, no ?
@bakkot What did you reserved for "protected" and "friend" and others ?
@doodadjs The language reserved a number of keywords a long time ago, well before we had an idea of how or whether we'd use them, because we thought we might later use them for features which had not yet been designed. package
is similarly reserved, but the ultimate syntax for the module system did not end up using that keyword. It happens.
We've discussed these issues extensively on other threads. Using the proposed syntax isn't what one might initially imagine, but it's a carefully considered trade-off that TC39 is willing to take. Is there something different to discuss here?
@littledan Probably what I've suggested at comment #361110146, and better.
@doodadjs that won’t work at all, because it would make even the existence of a private field automatically observable from outside the class code - which defeats the point of “private”.
@ljharb No, the point of "private" is don't make the data available to the outsiders. In no way "private" means to hide the existence of a field... Where did you get that idea ?
EDIT: Was missing "to the outsiders".
Absolutely it does, and it’s one of the most important parts of this proposal. The addition, removal, or modification of any private field must be 100% unobservable from outside the class (not counting Function.toString, of course).
@littledan Please point me to those threads to review, this seems at this point an unwise decision.
Edit: I'm linking this comment. I've provided links to three other comments for quick reference
A simple (granted limited) way of emulating private
for reference
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361021019
Conjured idea on how it might work, there might be better ideas for sure https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361033139
The pros of using a private
function that is has syntax sugar as well
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361034452
What follows address the question how one might recreate the following obj.#left.#right
in this comment here
https://github.com/tc39/proposal-private-fields/issues/14#issuecomment-323007685
@bakkot
I looked over that forum, thanks for linking it.
I noted a comment you made
The former is technically feasible but, to my eye, a lot uglier than #x.
Again, though, I think that both of
having private.x refer to a private field of this when inside of a class, and a public property of the variable private when outside, and having to write
private(private(obj).left).right
instead ofobj.#left.#right
would be fatal to anything like this proposal.
Looks to me like an opportunity to add a syntax to pass an object to a method call. Given the |>
one could do this
obj ▹ private ▹ .left ▹ private ▹ .right
It is so clear here what is going on, an object is being passed to private
, then the .left
property is accessed and that in turn is passed to private
to access .right
. I know that we can't pass an object to a method in JavaScript just yet, but we should be able to. That would be powerful.
const allTheRights = listOfObjectsPrivateOrPublic.map( obj => obj ▹ .right)
If anything having #foobar
is anti-pattern in that it makes this easy task hard.
If anything we should work to make the above syntax possible because of its broader implications. Solving a problem here being but one application.
I'd rather see #
used as a powerful operator. One with broad implication. Something that will benefit everyone in every 5 to 10 lines of code that they write, once they learn to break down their problems correctly.
Its not just about taste but direction. Regarding taste however, I have noted that you, like me, do bring up taste as a driving force in favor or this syntax; why is that better grounds than the taste against?
Anyway, even without this futuristic syntax, still presuming the|>
operator (Stage 1), first we define a simple function
const getProp = prop => obj => obj[prop]
Then do
private( obj ) .left ▹ private ▹ getProp('right')
To my eyes it looks better than #
to solve a problem solved this way just fine:
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361021019
Besides this:
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361034452
Is better. It allows a clear path to share code (portable).
Keep in mind also that there are entire languages that don't even have private fields, they rely on convention (ie, Python and the double __ underscore naming convention) and people write extremely advanced machine learning AI that could go from zero to best chess player in history in just hours using elaborate libraries in languages.
It is an unproven opinion that private fields are of such major importance that we must simply accept the use of #
for mere "yes but it looks good to my eye" meaning "I concede that this is not a rational decision but I just like it and that's that."
I'm not saying don't have private fields, by all mean. I'm just saying don't ruin it for everyone not inside this circle.
The clear reason against it is that once #
is used it taints the scope, fluidity, and power that it may have in the future, and that is a real concern.
@littledan I don't think so, especially given this is stage 3.
@babakness
It is so clear here what is going on
I disagree rather strongly.
I'd rather see
#
used as a powerful operator. One with broad implication. Something that will benefit everyone in every 5 to 10 lines of code that they write, once they learn to break down their problems correctly.
As we've said before, I think if you have a better use for #
you should propose it. We can't indefinitely hold things back because someone might someday invent a better use for similar syntax.
But I'm not sure that JavaScript has room for such an operator. We're constrained by the language as it already exists; there's only so much we can tack on to it.
Keep in mind also that there are entire languages that don't even have private fields, they rely on convention (ie, Python and the double __ underscore naming convention) and people write extremely advanced machine learning AI that could go from zero to best chess player in history in just hours using elaborate libraries in languages.
We went to the moon on assembly. I do not find this a particularly persuasive argument.
It is an unproven opinion that private fields are of such major importance that we must simply accept the use of
#
for mere "yes but it looks good to my eye" meaning "I concede that this is not a rational decision but I just like it and that's that."
Absent science we're not really in a position to do, all language design is opinion. This doesn't mean we shouldn't ever do any language design.
It is so clear here what is going on
I disagree rather strongly.
I disagree with your disagree even more strongly
Playing Devils Advocate... One pet peeve of mine in JS is assigning a variable key on an object.
const foo = {
[bar] : true
}
Why not this
const foo = {
#bar : true
}
Where #
means use the variable bar
here. Why not? It looks better, no?
Or maybe there is some contrived reason we can't have
obj ▹ .foo
So then why not use #
for this
obj ▹ #foo
So we can pass object to methods of the object
Or what about treading statements as expressions
const foo = # if (bar) { true } else { false}
And so on. I totally disagree with the justification with #
for private fields because you think it looks good but have no real reason to use.
BTW, the thumbs up thumbs down is childish. This is obviously a forum most looked at by people behind this proposal, hyped up about ramming this thing through, and I expect some degree of disagreement.
To your first one: why not? Because computed property keys are for more than just variables; they support any expression. Also, no, it does not look better. The brackets make for a beautiful and elegant symmetry with normal bracket access on objects.
Treating statements as expressions is already a proposal; it’d be do { whatever }
.
Emoji reactions are ways to indicate agreement or disagreement when words would just be noise; I’m sorry you think this widely used community convention on Github is childish.
Let’s try another tack. What single-character token, that’s currently a syntax error, and doesn’t already have a conflicting meaning in JS, would private fields use if not #
? If that’s in fact the only one this important proposal can use, then that’s the good reason to use it.
Maybe
MyClass#member
be a shorten for
MyClass.prototype.member
?
@doodadjs although that’s a notational convention that’s often used for just that purpose, i don’t think that in practice people need to often refer to prototype properties, certainly not enough for it to be worth new syntax.
@ljharb But "private" is rarely used too, you should focus more on "protected".
@doodadjs Even if that were true, which I dispute: you can build protected on top of private, but not the reverse. Generally we try to provide the necessary foundation for building on, rather than simply satisfying the most common case.
To GitHub: Please give at least 5 minutes before emailing :)
@bakkot: I'm sorry, but how do you build "protected" on top of "private" ? Do you have an example (or a link) ?
@bakkot What is wrong with comments alluded to here
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361132678
It looks better but also works better. foo.#bar.#silly
prevents doing
const allTheRights = listOfObjectsPrivateOrPublic.map( getProp('right') )
And the private
way is portable per comment here
https://github.com/tc39/proposal-class-fields/issues/77#issuecomment-361034452
Because you'd have to write logic to deal with this mess. private
as suggested by forum you linked is far superior to the "hashtag" syntax proposed as demonstrated.
@ljharb Because one isn't needed. Please address above linked comment.
Suggesting a single key is to suggest we need a single key. We don't. Why not use ##
. It looks just as funny as a single #
, is just as bad as a design choice, but has the advantage of not obstructing the future.
Still not sure why new keywords can't be added, such as TypeScript's private
to indicate private members? Seems more sane than a #.
I read the FAQ and did not see a compelling argument for not adding new keywords. The vague mentions of backwards compatibility I don't feel hold much weight. New breaking changes have been added several times to the language.
A private
keyword is no more or less "breaking" than the #
"keyword", but with the added benefit of not being confused with a trend I've seen recently for documentation of libraries to document an object's public members with the #
symbol.
Example: https://momentjs.com/docs/#/get-set/day/
Moment#date is for the date of the month, and Moment#day is for the day of the week.
Furthermore, it simply isn't consistent with other keywords in the language.
class
constructor
super
extends
async/await
#
Bizarre, really.
@lloydjatkinson I'm sorry the FAQ was did not sufficiently convey our reasoning. I don't really know how I could phrase it any more clearly, but you might find it useful to read some of the previous discussions on exactly this topic.
The vague mentions of backwards compatibility I don't feel hold much weight.
Well... we do.
Edit: Please see my next message
A camel is a horse designed by a committee.
Two reasons taste and utility.
So I confess that to me, this is just straight up ugly. Can there be something other than the '#' character? Why not just a keyword
Where "whatever" is a clean word that anyone reading would understand and is also not painful to the eyes.