Closed trusktr closed 5 years ago
Is the difference between these two snippets so big that it requires new syntax?
class A {
define foo = 2;
set foo = 2;
foo = 2;
}
class A {
@define foo = 2;
@set foo = 2;
foo = 2;
}
Hmm, yeah, the only extra work is needing to import them in every file. Seems like set foo = 2
would be nice and easy.
What about
class A {
@Private foo = 2;
@Protected foo = 2;
}
If we can achieve both protected and private (with hard privacy assuming the decorator doesn't leak it) using decorators, maybe we don't need privates in the spec yet, and we can let decorator implementations show which private/protected implementations are the best.
Can hard-private be achieved with decorators only?
I don't think so, since in JavaScript there isn't the concept of "privileged access" yet: everything that you can access from inside the class you can access from outside of it.
We discussed separate syntax for set and define at TC39 and in some other threads, maybe :=
for define and =
for set. Ultimately, giving a choice point so prominently in the syntax seems to add a lot of complexity that doesn't pay for itself (since most use cases are unaffected). Nevertheless, as @nicolo-ribaudo points out, this can be accomplished via decorators.
Closing the issue as this topic has already been discussed at length, and the readme clarifies the Set vs Define decision.
@nicolo-ribaudo
Is the difference between these two snippets so big that it requires new syntax?
Given that in the absence of all tooling, one would be reasonably expected to work and the other wouldn't, yes. The difference would be huge.
@trusktr
Can hard-private be achieved with decorators only?
Yes it can. It requires a protective membrane, but it can be done. I'm working out the details right now.
@littledan
I don't think so, since in JavaScript there isn't the concept of "privileged access" yet: everything that you can access from inside the class you can access from outside of it.
I would say it is possible. If I can make private
in lowclass
, then I'm sure it's possible with decorators. I haven't tried the decorator approach yet though. Honestly it seems like it'll require uglier bookkeeping, but still doable.
@littledan
Ultimately, giving a choice point so prominently in the syntax seems to add a lot of complexity that doesn't pay for itself
I would agree in most cases but this is the case where the added complexity will prevent foot guns, and make things explicitly clear, considering that the problem stems from ambiguity (people setting properties without knowing how they're set ("set" being either [[Define]] or [[Set]]).
IMO define
and set
keywords are better than :=
and =
, but I could definitely live with =
being [[Set]], as honestly that seems like a safer default, especially when extending base classes.
I don't think so, since in JavaScript there isn't the concept of "privileged access" yet: everything that you can access from inside the class you can access from outside of it.
@nicolo-ribaudo That point is moot because I achieved private fields in lowclass
; they are not visible outside of the class.
Protected is also perfectly achievable. In https://github.com/tc39/ecma262/issues/1341#issuecomment-439589889, @devsnek pointed out a way to gain access to protected members in my implementation. This is due to the fact that all constructors of every class are available on instance.__proto__
.
In the following comment, I replied with ideas on how to prevent it.
The main thing is, a JavaScript engine can encapsulate information in ways that aren't accessible in JavaScript, and the problem of constructor
being accessible is a simple problem solved inside the JS engine.
My implementation proves that protected is possible (considering the extra advantage an engine has over plain JS).
Is it possible to modify your library such that it can access protected and private using this.SOMETHING
? It is an important goal of the committee (https://github.com/tc39/proposal-class-fields/blob/master/PRIVATE_SYNTAX_FAQ.md#why-not-use-privatex-to-refer-to-a-private-field-of-this-and-privatethatx-to-refer-to-a-private-field-of-another-object)
It would be possible to do it if caller/callee hadn't been deprecated in strict mode, or if some means of uniquely identifying (without call capability) a function in the call stack had been created to replace caller/callee. Short of this, there's no user-land approach capable of securely preventing private scope leak when using this.SOMETHING
notation.
Is it possible to modify your library such that it can access protected and private using
this.SOMETHING
?
What do you mean? As in, make my library work where we write this.protected.foo
to access a protected member from inside a class method where it is allowed?
The answer is:
But who cares if it isn't possible to implement that notation in JS? We're talking about a spec for engines, not JS libs.
There is an approach that comes reasonably close to identifying the function scope. It requires tracking all calls to monitored functions and recording the stable portion of the stack frame just before passing the call through. Access attempts can then be checked with a stack frame comparison. If there's not a sufficient match, then you're not in the monitored function and the access will fail.
This isn't reliable though. Unfortunately ES leaves stack reporting up to the engine. So there's no guarantee that the stack information contains enough data to be 100% certain. For V8 based stacks, it works well as the file and line numbers are reported. For WebKit based stacks, only the function name is reported, so it's far easier to spoof.
@trusktr
But who cares if it isn't possible to implement that notation in JS? We're talking about a spec for engines, not JS libs.
The point is that the feature needs to be reasonably modeled somehow so developers can play around with the concept. If they can't come up with a reasonable implementation in Babel, it's not as likely to gain acceptance from TC39.
It requires tracking all calls to monitored functions
Good luck with that huge rabbit hole! :) It would need instrumentation of all code in the entire application, otherwise it will fail.
If they can't come up with a reasonable implementation in Babel
Babel can instrument the code it touches, but not other code (f.e. something downloaded from a script's src attribute, separately from the Babel code). You could make a ServiceWorker for instrumentation, but good luck with that too!
It's not possible in plain JS, without instrumentation.
For playing with it, instrumentation from a Babel plugin is perfectly fine (acknowledging that the feature only works within the code handled by your Babel invocation), and this is fine for proving that it can work.
But we don't need to do that, we know lexical scope tracking would work, this is obvious.
If they can't come up with a reasonable implementation in Babel, it's not as likely to gain acceptance from TC39.
That's somewhat sad. Maybe TC39 should be the ones making the working proofs of concept. It makes little sense for someone to say "make the language this way, because we think it'll work", and then for engine authors to say "okay, we'll implement that without questioning you!".
Lastly, private fields in Babel have not been around long enough at all for the feature to be evaluated well enough.
Good luck with that huge rabbit hole! :) It would need instrumentation of all code in the entire application, otherwise it will fail.
Not really. The printed stack trace is essentially that already, especially if a long-enough stack trace is collected. The chain of functions recorded in the stack 2+ entries back will always have the same entries for all functions called from a given function. This means that once a given monitored class function has been called, you can trust that if the base entries on the stack are not the same, then you've entered a different function. Since the stack is recorded for each call, you can trust that a mismatch means you're not in a trusted function.... And that's all you need. Well, that and a secure, detailed stack trace.
Lastly, private fields in Babel have not been around long enough at all for the feature to be evaluated well enough.
True enough, but apparently a moot point. The ship has sailed, and the crew has invested too much to admit they've made a mistake without the community at large utterly rejecting it. That's not likely to happen because they've bound it together with public fields, producing something that is not MVP, but will be desired by enough developers that there won't be a large enough rejection of the entire proposal. If they were to try and release private fields after public fields, I think they're well aware that the community wouldn't accept.
If they were to try and release private fields after public fields, I think they're well aware that the community wouldn't accept.
Somethings wrong. Is it about egos? Why do they want to rush it? Why force it on users who don't approve, and who want something better? Why shrink the syntax space with something that may not be a good fit?
To be both honest and fair about it, I think they actually believe they're releasing the best possible candidate for syntactically supporting data privacy in ES. I say this with many caveats. But of all the proposals, counter-proposals, demands from various board members, limitations from engine developers, and sheer political capital involved, this is the only proposal that didn't get 1-shotted by one of the TC39 members.
Do they think it is technically the best possible option? I doubt any of them thinks it is, but I do believe that they believe it's the best they could concede on given the considerations they were up against.
What about letting community vote and have a say on whether it moves forward or not. Sure, it won't move forward, but if that's what the community wants, then why not?
Why force it?
If privacy is wanted really really badly by some few people at TC39, can't they just use WeakMap
?
Maybe, the community is actually right! Maybe this feature shouldn't be released.
I'm not on TC39, but I want privacy. A class without the ability to hide data from the rest of the program is little more than an interface to me. There are many others who feel the same way. What I just can't wrap my mind around is why TC39 is so willing to push forward with a proposal that even the original author tried to back out of at one point. Technically superior methods have been proposed, but each was shot down for some undisclosed reason, or simply dismissed without even so much as a technical review.
So, for whatever reasons, we're going to be stuck with this new "the bad parts" candidate. Having married private fields to public fields virtually guarantees that there won't be enough complaints to reproduce the Observer
redaction.
but I want privacy
WeakMap can do the job perfectly fine at the moment. And it is brand new! There's really no need to rush the private fields just for syntax.
I just can't wrap my mind around is why TC39 is so willing to push forward with a proposal that even the original author tried to back out of at one point.
🤔
WeakMap can do the job decently, if you're skilled enough to remember all the extra steps and don't mind the lack of syntactic ergonomics. That's what this proposal was supposed to solve, among other things. Unfortunately, some requirements of questionable necessity were added on top, leading to the current proposal. It's not that those requirements were bad. There's good reasons behind each of them. Rather, it's that the resulting trade-offs cannot be consistently claimed to be in the best interest of the majority of the community, or the language itself.
It's expressive and clear, just like
async
/await
. Seems like having a choice would be great.I expect most of the time personally I will use
this.x
inside constructors because I want to ensure that super getters/setters work, if we don't get a statically-analyzable[[set]]
option.