-
Notifications
You must be signed in to change notification settings - Fork 113
Turning "hard private" into "soft private" #189
Comments
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 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:
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 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 In our product, we're already transpiling the very popular 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 |
Unintentionally depending on a library's "soft-private" api is easier than you'd think. For example: My software depends on // node_modules/[email protected]
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, // node_modules/[email protected]
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/[email protected]
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?
|
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 ( |
@superamadeus This is a really helpful example. For this particular example, you could make |
(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;
That would make this kind of a unique-to-javascript concept, but the |
@jwalton I don’t really see what this gains. The syntax with the 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. |
@jwalton This can be solved by introducing a new operator like |
@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 It's very natural to try to compare |
@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. |
@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 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. |
This comment has been minimized.
This comment has been minimized.
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 tc39/proposal-decorators#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. |
This is an off topic comment about a shorthand syntax for symbols. Please disregard.Sorry I haven't gotten around to responding much. A lot to take in.
I always pictured there would be a shorthand syntax for symbols. And a way to define context-unique symbols. This would be cool: class Example {
[:symbolProp] = "hello";
[::classSymbolProp] = "world"
}
class Test extends Example {
[:symbolProp] = "yo";
[::classSymbolProp] = "peeps"
}
// desugared:
class Example {
[$_1] = "hello";
[$_2] = "world"
}
const $_1 = Symbol.for("symbolProp");
const $_2 = Symbol.classSymbolFor(Example, "classSymbolProp");
class Test extends Example {
[$_1] = "yo";
[$_3] = "peeps"
}
const $_3 = Symbol.classSymbolFor(Test, "classSymbolProp");
console.log($_2 === $_3) // true Where |
Ok. So I buy the desire for more ergonomic shorthand around Symbol. Isn't this a problem for later? We already have the ability to do soft-private using Symbol. Creating a shorthand for it should be a proposal all it's own. From what I see, this doesn't have any bearing on what syntax is used for private data. |
Yes, it was completely off topic. I'm sorry. |
I think there's a case to be made (and perhaps @zenparsing already proposed this to the committee...in any case I'm sure someone has suggested it) for Regardless of all of this, it seems the majority opinion of the committee is that hard private is a better default for private fields anyhow. Personally I don't have a strong opinion about what the default is for private fields as long as both hard private and soft private are ultimately available with ergonomic syntax. |
@rdking from what I've seen so far - shorthand syntax for all kinds of Symbols (public and private) could become a deal-breaker, which may move committee from current proposal to Previously I was focused on solution for classes only, but later I got an idea about shorthand for all Since #183 shows that we can preserve most important |
@Igmat If it meant abandoning this excessively problematic proposal which trades away more value than it brings, I'd be all for researching syntax for simplifying Symbol declarations. @zenparsing's "investigation" presented some good ideas. The syntax would have to be different since it seems |
OK, I think we've addressed the concerns raised in this thread:
For these reasons, I'm closing this thread. |
@littledan @bakkot @zenparsing @lifaon74 @rdking @Igmat I would like to follow up on the discussion we were having about hard vs. soft private in #203, and this thread seems like a good place for it. I realize this issue in general has been discussed at great length in many threads over the past few years, but I think it's still worth discussing the consequences of shipping class fields while decorators are still in development, vs. waiting and releasing both together. IMO it should be fine to go ahead and ship class fields without waiting for decorators. (BTW, let's keep this thread on topic; obviously there are other objections to this proposal moving forward besides the hard private aspect.) As I pointed out in #203, Babel makes it possible to use both class fields and decorators today, and not all users need soft private or protected. In the future, for unmaintained libraries that might need to be patched in a way that requires access to private fields, there's always the option of forking, which is probably a good idea anyhow if the lib is no longer maintained. I didn't form this opinion lightly, and I do have concerns about defaulting to hard private (especially in the absence of equally or nearly equally concise syntax for soft private, which we may or may not get with decorators and/or a proposal to add sugar for symbols that isn't yet stage 0). I see it as a grand experiment in some ways; I can't think of any other language with classes that doesn't provide some form of reflection (enabled by default) for private members, so regarding classes specifically it seems we're going into uncharted territory. On the other hand, JS already has closures and modules, and there are plenty of libs that already hide implementation details and internal variables. Still, truly inaccessible private object state is something new, and I think it's hard to predict with 100% certainty whether the effect of hard private fields will be more positive or negative for JS users as a whole compared with soft private. But in other languages like Java that have reflection by default, obtaining the source code of your dependencies and forking them is often not an option. With JS it's obviously different, and hard private also brings a big benefit to maintainers of popular libraries who want to ensure that users can't depend on implementation details—an important ask from library authors for reasons that have been discussed here often. There is of course the risk that some developers (including library developers) authoring classes won't fully think through the consequences of using private fields without providing sufficient hooks for customization, but at least for open-source libraries, I think the open-source process (including the option to fork) will address this. Getting back to the question of release dates, it's important to consider that the longer it takes for this proposal to be finalized, the longer it will take before class fields can be used in all modern browsers without the use of a transpiler. There are many people who would like that day to come sooner rather than later, even if they have to wait longer for decorators to also be natively available. And unless the feature of hard private by default were dropped entirely, I don't see how waiting until decorators are ready would be helpful to those who want to be able to monkey-patch things either. |
@mbrowne I don't know what benefit there is in discussing this further. I've grown quite weary from frustration, not because things didn't go the way I wanted, but rather because the information I desire to help me understand the perplexing decisions that were made isn't forthcoming. That's not to say they didn't try. But that makes it more frustrating, realizing there's a gap somewhere, but not understanding how to bridge it..... But seeing as this is probably a "just for fun and edification" type conversation, I'll join. There are so many misunderstandings in that 3rd paragraph:
Yes, soft private definitely could use some syntax sugar. There's no 2 ways about that. However, soft private is still so much easier to set up than hard private as to be somewhat ridiculous. Here's the same class, 1 soft, and 2 hard. //Soft Private
let SP = (() => {
const data = Symbol("Private:data1");
const data2 = Symbol("Private:data2");
return class SP {
constructor() {
this[data1] = 42;
this[data2] = Math.random() * Math.PI;
}
print() {
console.log(`My private data1= ${this[data1]}`);
console.log(`My private data2 = ${this[data2]}`);
}
};
})();
//Hard Private: memory conservative
let HP1 = (() => {
const pvt = new WeakMap;
return class HP1 {
constructor() {
pvt.set(this, {
data1: 42,
data2: Math.random() * Math.PI
});
}
print() {
let p = pvt.get(this) || {}
console.log(`My private data1= ${p.data1}`);
console.log(`My private data2 = ${p.data2}`);
}
};
})();
//Hard Private: Babel approach
let HP2 = (() => {
const data1 = new WeakMap;
const data2 = new WeakMap;
function getPrivate(key, map) { return map.get(key); }
function setPrivate(key, map, value) { map.set(key, value); }
return class HP2 {
constructor() {
setPrivate(this, "data1", 42);
setPrivate(this, "data2", Math.random() * Math.PI);
}
print() {
console.log(`My private data1= ${getPrivate(this, "data1")}`);
console.log(`My private data2 = ${getPrivate(this, "data2")}`);
}
};
})(); Soft private is already less complex to setup and maintain than hard private, is faster, and costs less memory as well. Something needs to be done to improve hard private. Hence the need for the language-based upgrade.
I don't know how long you've been programming, but C++ and Java both existed for several years without that ability, and we're no worse off because of that. What we learned back then was that it's better not to rush through planning your code design, something that modern developers seem to have not been taught. This is old territory, and well mapped.
Run the code above in Chrome debugger and see if you can figure out how to access (or even see)
It's important to consider that class fields will still require use of a transpiler to make use of decorators to fix some important and fairly common use case problems even after being released. If you've got a flat tire with 6 fixable holes, do you rush out to go fix 1 hole? No. you wait until you can afford to either fix all 6 or buy a new tire.
I see it more like this: if private fields were removed from class-fields, then it wouldn't be so bad to release it now. Not good, just not nearly so bad. Sure it has issues, but they are mostly easily mitigated if you're aware of them. So I wouldn't have sufficient reason to want it delayed. The real thing worth having in this proposal is private fields, though. Unfortunately, the formulation is simply no good. While it has no directly observable effects, it has far too many observable side-effects. As such it does not fill the need. That has nothing to do with whether or not it's hard or soft private, though. |
As explained in other threads, TC39 doesn't tell anyone when to ship. Class fields advanced to Stage 3 more than a year ago, and I'm trying to get decorators to Stage 3 as soon as possible. (I was hoping to work on decorators all day today, but ended up spending much of the time on this repository.) |
This is an inappropriate and uncharitable comment; please review our Code of Conduct. It would be a wise idea, and a show of good faith, to self-moderate your comment, so that it doesn't need to be hidden. Thanks! |
@rdking, "Maybe you haven't been programming long enough." comes across as really dismissive. If what you meant to say is "I believe you are mistaken on a technical question about the history of other languages", please say that rather than disparaging other people's experience. It is not always the case that people who are inexperienced are mistaken or that people who are experienced will necessarily agree with you, and even when correcting someone you should avoid implying they'. (As a reminder, this repo is governed by TC39's code of conduct.) |
There's so many assumptions here, I don't know where to start! |
A side note to all about me: I tend to word things very... bluntly. Feel free to call me on it if it's too much. Even after 40+ years, I'm still working out the kinks. In either case, there's no need to "read between the lines" with me because I like putting everything on the line. (Double entendre intended.) |
Edited. Does that work better? |
This comment has been minimized.
This comment has been minimized.
soft private is still so much easier to set up than hard private as to be somewhat ridiculous.
I am well aware of how hard and soft private can be implemented today. When I said, "I do have concerns about defaulting to hard private", I was talking specifically about the new `#` syntax introduced by this proposal and how it should behave natively, not how you would polyfill it. I'm not sure if you were referring to polyfilling or something else, but in any case you seem to have misunderstood my point.
Truly inaccessible private object state is as old as factory functions
I am aware of this as well (and this is one of the things I was thinking of when I mentioned closures). I didn't mean it so literally...I was saying that private class fields with hard private semantics--with native support for ergonomic syntax--is something new. That's significant, because the verbose syntax probably dissuades a lot of people from using WeakMaps today even though they achieve the same goal. So I think there will be a lot more people using private fields than there are who are currently willing to go to the trouble of using WeakMaps (not to mention performance and bundle size).
…On January 8, 2019 4:25:31 PM EST, Ranando D Washington-King ***@***.***> wrote:
@mbrowne I don't know what benefit there is in discussing this further.
I've grown quite weary from frustration, not because things didn't go
the way I wanted, but rather because the information I desire to help
me understand the perplexing decisions that were made isn't
forthcoming. That's not to say they didn't try. But that makes it more
frustrating, realizing there's a gap somewhere, but not understanding
how to bridge it..... But seeing as this is probably a "just for fun
and edification" type conversation, I'll join.
---
There are so many misunderstandings in that 3rd paragraph:
> I didn't form this opinion lightly, and I do have concerns about
defaulting to hard private (especially in the absence of equally or
nearly equally concise syntax for soft private, which we may or may not
get with decorators and/or a proposal to add sugar for symbols that
isn't yet stage 0).
Yes, soft private definitely could use some syntax sugar. There's no 2
ways about that. However, soft private is still so much easier to set
up than hard private as to be somewhat ridiculous. Here's the same
class, 1 soft, and 2 hard.
```js
//Soft Private
let SP = (() => {
const data = Symbol("Private:data1");
const data2 = Symbol("Private:data2");
return class SP {
constructor() {
this[data1] = 42;
this[data2] = Math.random() * Math.PI;
}
print() {
console.log(`My private data1= ${this[data1]}`);
console.log(`My private data2 = ${this[data2]}`);
}
};
})();
//Hard Private: memory conservative
let HP1 = (() => {
const pvt = new WeakMap;
return class HP1 {
constructor() {
pvt.set(this, {
data1: 42,
data2: Math.random() * Math.PI
});
}
print() {
let p = pvt.get(this) || {}
console.log(`My private data1= ${p.data1}`);
console.log(`My private data2 = ${p.data2}`);
}
};
})();
//Hard Private: Babel approach
let HP2 = (() => {
const data1 = new WeakMap;
const data2 = new WeakMap;
function getPrivate(key, map) { return map.get(key); }
function setPrivate(key, map, value) { map.set(key, value); }
return class HP2 {
constructor() {
setPrivate(this, "data1", 42);
setPrivate(this, "data2", Math.random() * Math.PI);
}
print() {
console.log(`My private data1= ${getPrivate(this, "data1")}`);
console.log(`My private data2 = ${getPrivate(this, "data2")}`);
}
};
})();
```
Soft private is already less complex to setup and maintain than hard
private, is faster, and costs less memory as well. Something needs to
be done to improve hard private. Hence the need for the language-based
upgrade.
> I can't think of any other language with classes that doesn't provide
some form of reflection (enabled by default) for private members, so
regarding classes specifically it seems we're going into uncharted
territory.
Maybe you haven't been programming long enough. C++ and Java both
existed for several years without that ability, and we're no worse off
because of that. What we learned back then was that it's better not to
rush through planning your code design, something that modern
developers seem to have not been taught. This is old territory, and
well mapped.
> Still, truly inaccessible private object state is something new, and
I think it's hard to predict with 100% certainty whether the effect of
hard private fields will be more positive or negative for JS users as a
whole compared with soft private.
Run the code above in Chrome debugger and see if you can figure out how
to access (or even see) `data1` & `data2` in HP1 & HP2 instances. Truly
inaccessible private object state is as old as factory functions,
because that how we used to do it before WeakMap and `class`. Done
well, a new syntax for hard private is a plus, as much or more so than
a new syntax for soft private. What's missing is an approach that isn't
so riddled with complexities and issues.
> Getting back to the question of release dates, it's important to
consider that the longer it takes for this proposal to be finalized,
the longer it will take before class fields can be used in all modern
browsers without the use of a transpiler.
It's important to consider that class fields will still require use of
a transpiler to make use of decorators to fix some important and fairly
common use case problems even after being released. If you've got a
flat tire with 6 fixable holes, do you rush out to go fix 1 hole? No.
you wait until you can afford to either fix all 6 or buy a new tire.
> And unless the feature of hard private by default were dropped
entirely, I don't see how waiting until decorators are ready would be
helpful to those who want to be able to monkey-patch things either.
I see it more like this: if private fields were removed from
class-fields, then it wouldn't be so bad to release it now. Not good,
just not nearly so bad. Sure it has issues, but they are mostly easily
mitigated if you're aware of them. So I wouldn't have sufficient reason
to want it delayed. The real thing worth having in this proposal is
private fields, though. Unfortunately, the formulation is simply no
good. While it has no directly observable effects, it has far too many
observable side-effects. As such it does not fill the need. That has
nothing to do with whether or not it's hard or soft private, though.
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#189 (comment)
--
Sent from my Android device with K-9 Mail.
|
I get where you're going, and to an extent I agree with you. I tend to think that when there's a choice like this while designing a language feature, then both paths need to be taken. I.e. there needs to be ergonomic syntax for both hard and soft private. Leave the decision on what to use to the developer. Anything less is a disservice. You can easily guess how that way of thinking applies to the balance of this proposal.
We definitely agree here. I think that to a somewhat lesser extent, the same is true for Symbol-based privacy. Hence my thought that both should receive ergonomic syntax support. |
Hard privacy at runtime is not just about library authors preventing end devs from using certain features. Suppose we have a website. Do we know what user scripts a user will be sticking into the page? Think about how many people install plugins without caring about plugin permissions or paste codes into the console. I bet the number is higher than we'd like! Once our code is loaded at runtime (and we make sure our application ships with runtime-hard-privacy, then we'll be more protected against rogue scripts doing things we don't want them to do. @littledan The behavior that the private proposal in this repo has, has an aspect that works well: the privacy. Maybe if you (and champions of this proposal) can explain how other desires can be fulfilled in future add-on proposals, then people would start to like this repo's proposal more once they get passed the syntax. For example, how, in the future, can we:
? Regarding de-sugaring to If champions this repo can provide both the current proposal, and ideas for future add-ons that include features like above, then community dislike may lift up. It's just a guess though, and I'm assuming that people can get over having to use It may just be, that because the proposal doesn't really mention those parts, that people don't see the potential future. The functionality is more important than the syntax, as long as the syntax isn't terrible ( #206 is fairly simple idea, and it makes private work with anything (object literals, ES5-style classes, ES6 |
@trusktr you can't iterate WeakMaps, and the key of a WeakMap is object identity, so there's no dynamic way to get at a value. |
@ljharb You may hack the system by overriding WeakMap (and store the values in some case) |
Only if you're first-run code - otherwise, |
I know this was rejected, though I don't necessarily agree with the reasoning stated in the FAQ, but I feel it's necessary to point out that the first 5 points @trusktr has presented would be resolved trivially with the proposal of |
I meant I can iterate the properties of the private properties like so: const _ = privateHelper()
class Foo {
constructor() {
this.publicProp = 'foo'
_(this).privateProp1 = 'bar'
_(this).privateProp2 = 'baz'
}
test() {
for (const key Object.keys(_(this)))
console.log(key)
}
}
const f = new Foo
f.test()
// "privateProp1"
// "privateProp2" That would be similar to the following if there were some sort of syntax for it: class Foo {
publicProp = 'foo'
#privateProp1 = 'bar'
#privateProp2 = 'baz'
test() {
for (const key Object.keys(this#))
console.log(key)
}
}
const f = new Foo
f.test()
// "privateProp1"
// "privateProp2" or maybe for (const key Object.keys(this, #)) // give a function private access somehow Just throwing ideas out there. It'd be nice for these type of things to be easy. |
I'd be interested to know more about the findings of TC39's community outreach. I'm sure that something between private and public is something a lot of people are interested in. What else made the top of the list that isn't covered by this proposal? And in addition to decorators, are there any other proposals already in the works to expand on this proposal? |
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 tc39/proposal-private-fields#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.
The text was updated successfully, but these errors were encountered: