Closed jwalton closed 5 years ago
Hard private is not “defeated” if you edit the source - either manually or programmatically. The concern is about at runtime - if the code shipped to the engine has encapsulation, then it will be reserved (just like with variables inside a closure).
Really? I admit that installing a babel plugin might be, as you put it "unwise", but if a user can do it, they will, and here they certainly can. At the end of the day, JavaScript is an interpreted language; if you want to share a module with me, you need to share the source, and I can infer the existence of private variables by reading that source. So can my program. I can even write a loader that reads in your module and does this at execution time.
(Anyways, hopefully I at least got a grin out of you with my hashtag comment. ;)
If they’ve done that, they’re not actually using the feature. Actually using the feature - shipping the code using private fields that are not intentionally exposed - results in “hard private”, which is what matters.
Isn't this is like saying "If you type otherObject._something
in python, you're not using the features in PEP 8 - actually using the feature, and not typing the _
, results in hard private"?
I mean, the "feature" here is a feature for library developers to prevent library users from accessing private members in their library. It's not really a feature for library consumers at all. If there's a simple way for library consumers to get around that protection, I'd argue that it's not "hard private".
A source rewriter isn’t a simple way, and most importantly, it doesn’t work at runtime.
People will have the source rewriter installed if they even want to use a module that uses private variables, at least for the next several months.
And why do you think it doesn't work at runtime? Babel is just a javascript program; it's as simple as replacing your import with:
const moduleSource = await fetch('https://domain.com/bobsPackage.js');
const transformed = babel.transform(source, {
plugins: [privateToPublic],
});
const bobsPackage = eval(transformed);
console.log(bobsPackage._secret);
It even works in a browser.
@jwalton Thanks for the entertaining insight. It's good to highlight this.
My view is that it all comes down to boundaries. If you don't define boundaries, then you can make arguments that any encapsulation is not truly "hard" (not a true Scotsman) unless it's on an air-gapped machine that can only be communicated with via unequally-laden carrier pigeons.
The boundary of ECMAScript is the source text ingestion. So it's fair to say that details are not abstracted away from anyone that can tamper with code prior to ingestion. Therefore any judgement of whether something is truly encapsulated by ECMAScript should be assessed post-ingestion.
A secondary and lesser point: Real-life use of fields will tend to go through minifiers that will mangle private names in ways that (underscore-prefixed) properties cannot safely be mangled. This will act as a deterrent to casual/widespread use of the babel-plugin-private-class-fields-to-public approach because mangled names are harder to deduce and can change frequently.
A final and least important point (because it all just flows back to top-level philosophy): as an electrical OEM who manfactures computer PSUs locked by uncommon star-shaped screws and covered with "danger of death" stickers, changing the internals of the PSU is a reasonable thing to do. If a user gets inside and has a bad experience because they didn't understand the updated internals, we all know who is at fault. If that same PSU just has the stickers but no physical lock, it would be more of a grey area and some (in places with consumer regulations) would argue it's an accident waiting to happen. The Babel plugin is an invasive power-tool akin to a star-shaped screwdriver IMO.
@robpalme Thanks for the insightful comments. These are all fair points.
And, one deterrent to the use of babel-plugin-private-class-fields-to-public
that you missed, at least in the short term, is that most libraries published to npm have already been through babel
once, and don't have their original es6 source published; if your private fields have already been turned into a WeakMap, then there's no way for my little babel plugin to find them.
But, in reference to your last point; I would point out that in, say, Java, once you break out the reflection API, you're pretty clearly tearing through "danger of death" stickers. You have very definitely violated the contract put in place by the author of the module you're using, and you know that if bad things happen later on, it's totally your fault. So I would call Java a "hard private" language, if this is where we're drawing the line. (Is Python like one of those cheap "Warranty Void If Removed" stickers that's already started to peel up at the edges when you take the product out of the box?)
But we software types (as a generality, and like all good generalities it's not always true) tend toward the tinkerer type - the sort of people who think up a pretty funny joke about hashtags and private members, and then spend an hour learning how to write a babel plugin just so they can make that joke. Folks like us are going to break out the power tools and star shaped screw drivers from time to time.
Ultimately, I guess, the point of this issue is, if we're going to make design decisions and say "we will make this extra complicated in service of the very important goal of hard privacy," but then we don't actually get the benefits hard privacy, was that complexity worth while? The answer to that question - where and what tradeoffs are "worth it" - is going to be different for everyone, of course, and maybe it is worth it. But hopefully I've given people something to think about over the holidays; once this makes it to stage 4, it's forever.
I'd say that running a Babel transform like this is a way of forking your dependency, which is a reliable way to get at private state. The key is, when forking a dependency, you are taking on the burden of maintaining that fork. By contrast, many large JS projects have had to roll back changes when there were too many compatibility complaints from people accessing "internal" APIs. I'd say this is a qualitative difference.
@littledan I understand the point you're making, but I think the line you're drawing here is a little murky, and is only going to get less clear as time goes on. Babel added the overrides
keyword to their config specifically to make it easy to transpile dependencies.
In our product, we're already transpiling the very popular debug
library because they now publish code with es6 features to npm. Our toolchain for an older part of our product has an ancient i18n string extractor, written by a former co-op, which is totally unmaintained and breaks on the const
keyword, so we transpile debug
all the way down to ES5, so it doesn't crash our i18n preprocessor. We don't even rely on debug
directly - our dependencies rely on debug, but that's enough to get the code into our bundle. (Wow, I really need to fix that. That's something I should work on while I'm at my in-laws for the holidays.)
But my point is, there are a lot of advantages to transpiling dependencies, and it's only going to get more popular to do so as time goes on.
And again, to look at this from a purely practical standpoint; if there's a library foo
, and foo
's users constantly violate it's privacy restrictions, that's a pretty good indicator that the author of foo
is perhaps being a bit too restrictive in what they expose. If, in order to be useful, users of foo
need to violate privacy restrictions, then they will figure out a way to do so. Life finds a way. If people are violating the privacy restrictions en-masse, and the package maintainer makes a change but it breaks the Internet, Wreck-it-Ralph style, then on that day when we are all huddled in our cold, dark homes, unable to control our IoT thermostats and lights, when Alexa no longer fills our orders for milk and we are unable to feed ourselves, will it really matter if we can say "This is our own fault, because we didn't have hard private variables in JavaScript, and someone posted a popular stack overflow answer that told people to use foo._widget
!" Or will we somehow be saved if we can say "We gave the world beautiful perfect hard privacy. It's totally not our fault that someone posted a popular stack overflow answer that told people to install a babel plugin and /then/ to use foo._widget
"?
Unintentionally depending on a library's "soft-private" api is easier than you'd think. For example:
My software depends on library@1.0.0
.
// node_modules/library@1.0.0
class Library {
/* ... */
getBooks() {
return [ /* ... */ ]
}
}
// index.js
class MyLibrary extends Library {
getBooksByAuthor(author) {
return this._getBooksWithFilter(b => b.author === author);
}
_getBooksWithFilter(filterFn) {
return this.getBooks().filter(filterFn);
}
}
Ooh, Library@1.1.0
was released! I'll upgrade because features I want have been added in a non-breaking way (according to semantic versioning).
// node_modules/library@1.1.0
class BookFilter {
applyFilter(books) {
return books.filter(/* ... */);
}
}
class Library {
/* ... */
getBooks() {
return [ /* ... */ ]
}
_getBooksWithFilter(filter) {
return filter.applyFilter(this.getBooks());
}
}
Suddenly my software is not working as expected. How is this possible? It was a minor version change! This library is unpleasant!
@superamadeus This is a good use case for symbols.
@zenparsing Alternatively, private fields.
Edit:
// node_modules/library@1.1.0
class BookFilter {
applyFilter(books) {
return books.filter(/* ... */);
}
}
class Library {
/* ... */
getBooks() {
return [ /* ... */ ]
}
#getBooksWithFilter = (filter) => {
return filter.applyFilter(this.getBooks());
}
}
Note that the semver spec is a bit ambiguous about whether that change can be semver-minor - are symbols and underscores part of the documented API or not? If by “documented” you mean “what i can see in the repl”, yes - if you mean “what’s written in prose in another place” then probably not. I interpret it in the former way, to protect my users from any unintentional breakage; some authors are less cautious. Private fields make this unambiguous.
In my opinion symbols are a better fit for the library use case.
Why?
@zenparsing
I appreciate your opinion and see where you're coming from. That said, I think both options (symbols vs. private fields) are viable and would require forethought about the tradeoffs. But neither solution will work for 100% of the use cases.
In my opinion symbols can get verbose:
// private field
class Service {
#myMethod1 = () => {};
#myMethod2 = () => {};
#myMethod3 = () => {};
#myMethod4 = () => {};
#myMethod5 = () => {};
#myMethod6 = () => {};
#myMethod7 = () => {};
#myMethod8 = () => {};
#myMethod9 = () => {};
}
// symbols
const myMethod1 = Symbol();
const myMethod2 = Symbol();
const myMethod3 = Symbol();
const myMethod4 = Symbol();
const myMethod5 = Symbol();
const myMethod6 = Symbol();
const myMethod7 = Symbol();
const myMethod8 = Symbol();
const myMethod9 = Symbol();
class Service {
[myMethod1] = () => {};
[myMethod2] = () => {};
[myMethod3] = () => {};
[myMethod4] = () => {};
[myMethod5] = () => {};
[myMethod6] = () => {};
[myMethod7] = () => {};
[myMethod8] = () => {};
[myMethod9] = () => {};
}
For the record, I'd rather use private methods (#myMethod() {}
).
@superamadeus This is a really helpful example. For this particular example, you could make getBooksWithFilter(self, filter)
a top-level function and just not export it. But, I can certainly think of cases where I have a function that I want to be a bound member function, like say a React event handler where I want a per-instance bound function so I can take advantage of PureComponent
. And there you are quite likely to have name collisions like this, because people tend to name their functions onSubmit
or other such boring things. :)
(And, of course, data instead of functions. Don't want to be stashing member data in global variables.)
@superamadeus That's why we ought to have sugar over the symbol-internal-to-module pattern.
If we had sugar over that pattern, and we assume that that pattern works for the library use case, then what use cases are left for hard private?
This made me think about this problem in a very different way. It sounds like what we really want here isn't "private" variables, but "class scoped per-instance variables". But we already have keywords for declaring block-scoped variables in JS; const
and let
:
class MyLibrary extends Library {
// Declare a per-instance variable that's only accessible within these {}s.
let getBooksWithFilter = (filterFn) => {
return this.getBooks().filter(filterFn);
};
getBooksByAuthor(author) {
// Note that `this._getBooksWithFilter` is undefined here.
return getBooksWithFilter(b => b.author === author);
}
}
That would make this kind of a unique-to-javascript concept, but the #
private thing is also kind of a unique-to-javascript concept. And it de-weirds things considerably - there's no this.#foo !== this['#foo']
, for example. The one downside this would have over the #
version is that you can't easily access these variables from other instances of the same class.
@jwalton I don’t really see what this gains. The syntax with the #
sigil is jarring at first because it’s new syntax but it’s very consistent if you don’t try to compare it to typical property access.
In the example you’ve shown, it is hard to tell at call site if you’re using a private member vs a global variable. I personally like knowing when I’m making a private member access.
Edit: but yeah, I think the private field mechanism is very similar to “class scoped per-instance variables”. I don’t see how what you’ve described makes any gains on the current proposed syntax.
The one downside this would have over the
#
version is that you can't easily access these variables from other instances of the same class.
@jwalton This can be solved by introducing a new operator like other::x
, or just use non-sugared form other[symbol]
if it's symbol-based.
@zenparsing @hax It was my (assumed) understanding that the committee had considered a symbol-based approach, and declined it. I can't find any documentation on this though. Anyone have any info on this?
That said, I still fail to see what you gain from the symbol based approach that you don't get in the current private field spec.
I'd love to see some examples of cases where the symbol approach provides benefits over the current private field spec, with respect to hard-private fields. I'm having trouble getting there on my own.
@superamadeus There's one thing that's always bugged me about the current private property proposal:
class Library {
#books = ['Snow Crash'];
listBooks() {
return this.#books.slice();
}
}
const library = new Library();
library.addBook = function() {
this.#books.push('Mistborn'); // Nope!
}
I'm told this doesn't work. Even if you define new functions on Library
's prototype, they can't access the private members of Library. That's really odd. I can't think of another language that's like that; private means not only private to the instance, but private to scope as well. The let
syntax is maybe functionally nearly identical, but I think semantically it expresses the intent much better.
It's very natural to try to compare #
to natural property access, because it looks like a property access, when really it isn't.
@zenparsing your conclusion rests on soft private actually being sufficient or good for libraries by default - my experience tells me that it is not, even if maintainers initially believe that it is.
@superamadeus
I'd love to see some examples of cases where the symbol approach provides benefits over the current private field spec, with respect to hard-private fields. I'm having trouble getting there on my own.
@jwalton points out one benefit: with symbols you don't need to cram everything that references the property into the class body. You can put the functions where they make sense.
Another advantage: with symbols you can trivially wrap an object with a Proxy and it works. With private fields, you have to create a complex membrane around it in order to avoid TypeErrors.
Another one: with symbols you can easily create a generic deep clone by using Reflect methods (like Refect.ownKeys
). With private fields, you have to embed cloning logic directly in the class body, for each class.
Or imagine trying to write your own "object inspector" in normal JS. With private fields you can't get to the data values to display them.
@ljharb Can you elaborate on why symbols aren't good enough for libraries?
@zenparsing any time something is accessible externally, it increases the api surface. Symbols are still accessible, and if it’s possible to depend on them, people will (and do).
I have dealt with tons of issues on many libraries i maintain due to people depending on things that weren’t intended to be part of the “public api”; in practice I’ve found that the only way to avoid both user breakage and maintainer support burden is to restrict what is possible, not to just create a false sense of security by “hiding them better”.
For an object inspector; I’d argue that that private data should never be displayed, just like you can’t display the closed-over variables that a function uses.
replying to #issuecomment-449425267
Unintentionally depending on a library's "soft-private" api is easier than you'd think. For example: ... Suddenly my software is not working as expected. How is this possible? It was a minor version change! This library is unpleasant!
that problem shouldn't even exist if you use static-functions instead of class-methods:
// node_modules/library@1.0.0 - using static-functions
var local = {};
local.libraryGetBooks = function (that) {
return [ /* ... */ ];
};
module.exports.libraryGetBooks = local.libraryGetBooks;
// index.js - using static-functions
var local = {};
local.getBooksByAuthor = function (that, author) {
return _getBooksWithFilter(that, function (b) {
return b.author === author;
});
};
local._getBooksWithFilter = function (that, filterFn) {
return require("library").libraryGetBooks(that).filter(filterFn);
};
thanks to static-functions, we can safely upgrade to library@1.1.0, even if underscored/"private" members are exported:
// node_modules/library@1.1.0 - using static-functions
var local = {};
local.bookFilterApplyFilter = function (that) {
return booksFilter(that, /* ... */);
};
local.libraryGetBooks = function (that) {
return [ /* ... */ ]
};
local._libraryGetBooksWithFilter = function (that, filter) {
return bookFilterApplyFilter(filter, libraryGetBooks(that));
}
module.exports.bookFilterApplyFilter = local.bookFilterApplyFilter;
module.exports.libraryGetBooks = local.libraryGetBooks;
module.exports._libraryGetBooksWithFilter = local._libraryGetBooksWithFilter;
this proposal overall is misguided, because its trying to fix an anti-pattern (inheritance-based programming) that poorly fits javascript's problem-domain (UX-worflow programming).
For the umpteenth time, that is not javascript’s sole problem domain, nor is inheritance-based programming an antipattern. Please stop commenting in ways that make broad sweeping statements about the JavaScript ecosystem that cause people to feel excluded, marginalized, and silenced.
@ljharb The friction introduced by symbols will certainly discourage the "mucking about" you are concerned with. My hypothesis is that the added friction will address almost all of the pain, at least for 99.9% of the libraries out there. Do we have experience with symbols that might prove or disprove it?
Sure, it's not a "complete" solution. But I think this is a case of "better is worse", because the "better" solution (i.e. private fields) comes with strings attached.
Oh no! When @ljharb said:
Hard private is not “defeated” if you edit the source - either manually or programmatically.
I almost replied with "Really? Where does it say that in the spec?" But it totally does!
no JS code outside of a class can detect or affect the existence, name, or value of any private field of instances of said class without directly inspecting the class's source
@littledan @ljharb looks like I owe you two a beer next time you're in Ottawa. :)
Happy holidays, everyone!
Do we have experience with symbols that might prove or disprove it?
Not directly, that I'm aware of, though for example several members of node (x, x) have said that Symbols are not sufficient. And given their experience I think it's fair for them to assume that people will monkey-patch Symbol-based stuff just as they do with _
-prefixed properties (and magic require
'd modules and so on).
Java's experience is also relevant. They recently introduced a major feature to the language (modules) in large part because it turned out that "you have to use reflection to get at it", which is what Java had previously meant by private
, was very much not sufficient to discourage people from reaching into internals.
Hard private is not “defeated” if you edit the source - either manually or programmatically. The concern is about at runtime - if the code shipped to the engine has encapsulation, then it will be reserved (just like with variables inside a closure).
This private fields proposal does let you defeat hard private without having to edit the source. It's definitely pretty obscure but possible with the return override trick.
@gsathya I don't think the return override trick is "defeating" private fields, as people have generally been using the term? It doesn't let you detect or affect their existence or value, it just lets you install them on things.
@gsathya I don't think the return override trick is "defeating" private fields, as people have generally been using the term? It doesn't let you detect or affect their existence or value, it just lets you install them on things.
Installing them on foreign objects will let you defeat brand checks.. which is exactly what library authors don't want happening, right? Maybe this isn't super useful but I'm just saying this proposal isn't infallible as people seem to suggest.
Installing them on foreign objects will let you defeat brand checks.. which is exactly what library authors don't want happening, right?
I don't think so? It seems like the main ask is for the class to have exclusive control over (and visibility of) the values of private fields, which remains the case. And the object will still have been run through the class constructor, which allows it to establish any other invariants it likes.
The brand check is to ensure the brand exists; if you use the return override trick then you correctly pass the brand check by indeed having the brand.
See https://github.com/tc39/proposal-class-fields/issues/179 for discussion about proposals to let a subclass call out to its original superclass (and still use private fields and methods and be extensible). It's hard to see how we could make that sort of integrity the default (even if we went with private symbols) without a bigger break for how subclasses work in JS.
I know a lot of people are ascribing branding as a fundamentally very important thing about private, but I always thought of it as a nice error checking feature, which we are trying to carry forward consistently in the world of private-related features.
I don't think the errors thrown from missing fields or methods are related to the choice of WeakMap semantics--private symbols could also trigger throwing errors more eagerly, and WeakMap-based fields could return undefined for missing private fields. They are just two design decisions that this proposal takes.
several members of node (x, x) have said that Symbols are not sufficient.
The first case is very interesting, if you read the whole story, you will find it's a interest conflict between the platform maintainers and library authors. IMHO, the real problem is bad communication, and the issue was solved after having communication. So "hard private" never solve the real problem, it just push the whole responsibility to one side. @jwalton have already showed how babel can be used to workaround hard private, and if authors follow this way, we still have the same risk like today, or even worse, because no one think we need communication any more.
@jwalton @superamadeus After looking over your comments in this thread, I'm curious to get your opinions on proposal-class-members. You're still going to find the same limited notion of hard private, but the mental model in use is that of a function closure. Let me know what you think.
I don't see why we can't have sugar for both hard private AND soft private. IMO decorators are a good solution for soft private, especially if https://github.com/tc39/proposal-decorators/issues/191 can be solved (preferably very soon after the release of the first version of decorators), and also probably the most practical solution since we want decorators for other reasons anyway. But they're not the only option. The important thing is that developers need an ergonomic way to specify either hard private or soft private, as needed. Some developers, particularly some library authors, care more about hard private. Many others really want soft private (and that's what I care most about as well, but I see the option of hard private where desired as a nice bonus). I think it has been well demonstrated that both are important.
can more people explain how hard/soft privates would fit into big-picture solutions (e.g. UX-workflows)? better understanding real-world javascript painpoints this proposal is meant to solve would provide stronger arguments for its design-proponents.
e.g. what designs of this proposal would practically benefit salesforce's locker service? or help streamline google's security audit of their javascript-products?
What exactly is the use case for soft-private that makes it such a sore point for developers? The only real use-cases I've seen all involve bad practices like monkey-patching and use of pseudo-private API. Unless I'm mistaking something, soft private just allows you a means of discovering the internal details of how a class works, possibly allowing you to modify them.
The reason I don't understand the want is because I have yet to see a single line of external code that tries to or would work better if it could access variables inside a closure. Regardless of the proposal, the core concept for private in ES is (or probably should be) to extend a closure around each instance of a class. In this way, the instance gets its own set of variable that cannot be accessed by closure outsiders.
So, short of using Symbols (which would be my vote for soft private), what's really being requested? Is it just a more ergonomic syntax over Symbols? Or are you really asking for something else?
@rdking The semantics of symbols are certainly sufficient for soft private. I think the two best options for providing it in an ergonomic way are either more concise syntax for symbol-keyed properties, or decorators on classes and/or individual private fields. I think this question really boils down to a larger question, "Why do we need reflection on class internals?" There are many use cases for reflection...for some use cases, reflection on public members is sufficient; for others, you need to be able to reflect and even modify private members. The second category is relatively rare, but I provided one example in https://github.com/tc39/proposal-decorators/issues/191.
The second category is relatively rare
Just to clarify what I mean by "rare", I mean the average developer might never encounter a need to do this. It's more likely that they would use a library that would use reflection under the hood. For example, if something like breeze.js wanted to make full use of JS classes in a future version for defining entities, the example use case I mentioned in https://github.com/tc39/proposal-decorators/issues/191 might be important. Or maybe some extension to Apollo client or Relay that allowed you to deserialize your GraphQL data to full-fledged objects with fields and methods defined in JS classes (currently such libraries give us the data as plain objects, but no methods). And obviously while only a relatively small number of developers are involved in maintaining those libraries, their usage is anything but rare.
Others could probably give other example use cases for reflection on private fields; this is just the use case I'm most familiar with.
In which we provide a fairly trivial way to bypass "hard private" protections on third party libraries, in practice.
Is this really "hard private"?
It seems like a lot of decisions in this proposal concerning private properties have been made specifically to support "hard private" properties. There was a long discussion here about whether this was a good idea or not, which didn't really seem to come to a consensus. TC39 is never the less proceeding with "hard private", and I see a decisions being justified using the argument "the alternative wouldn't really be hard private." This is stange to me, because the current proposal isn't hard private (at least in practice), and I suspect creating hard private properties is impossible in JavaScript.
What is "hard-private" anyways?
To quote @ljharb:
(We can have a whole argument here between the "programming by contract" people, and the "language purist" people, about whether that second statement is correct or not - in fact we did in https://github.com/tc39/proposal-private-fields/issues/33, so let's not have it again. 😉)
Obviously, nothing is really hard-private. I mean, the contents of that private variable are somewhere in memory, and I'm sure I can find some interesting clever way to get at them. I can write my own javascript engine, or write a node.js native module that peeks at memory, or I can use some esoteric attack like rowhammer to exfiltrate that data. What we really mean by "hard private" is that, in a "normal" javascript environment (not a debugger, and maybe in a browser), you can't get access to the contents of a private member without doing something extraordinarily.
The motivation for hard private seems mainly (?) to be for library authors; so let's say if a user can access private members in your library "easily", it's not hard private.
So, how "hard private" is this proposal?
PoC||GTFO
Bob writes an npm module:
Alice is using Bob's npm module, and realizes she wants access to that
#secret
variable. I mean, it's named with a hashtag, and hashtags are for sharing, right? (And, let's be honest, this is a pretty boring NPM module. What else is Alice going to do with it?)In order to get around this, Alice adds the following to her .babelrc.js:
babel-plugin-private-class-fields-to-public is a Babel plugin which transforms Bob's package to look like this:
Now Alice can write:
Mischief managed!
Wait wait. Isn't this cheating? Babel isn't a "normal javascript environment!"
Between
@babel/core
andbabel-core
, babel was downloaded around 11 million times in the past week. Not too many javascript programs these days don't go through babel. You could even run babel in a browser, and transpile third party modules on the fly.babel-plugin-private-class-fields-to-public
plugin was written in typescript, which for a while was a big babel competitor, but even that plugin was compiled with babel. Seems like a normal javascript environment to me.Even if you don't buy that argument, look at this from a practical standpoint:
In Python, private members are about as soft-private as you can get. Private members are members that start with an
_
. To get around this, you need to type the name of the variable, and maybe feel bad for a few moments.Java, on the other hand, is still soft-private by the "if it's accessible" definition above, but you have to do some work to access a private member. You have to go look up the refletion package and figure out how it works again (since the last time you used it was probably when you last had to get around a pesky visability problem). Then, when it doesn't work, you need to go to stack overflow to find out you forgot to call
setAccessible()
.With this proposal, as it stands, accessing private members in JavaScript falls somewhere between these two; you need to install a babel plugin, but after that it's basically "type _ and feel slightly guilty". Let's put it somewhere between Python and Java; maybe not as "hard private" as all that.