-
Notifications
You must be signed in to change notification settings - Fork 113
Please don't use #
for syntax (consider going back to stage 1)
#177
Comments
#
for syntax.#
for syntax (revert to stage 1)
#
for syntax (revert to stage 1)#
for syntax (consider going back to stage 1)
What about accessing private fields on other objects besides |
@ljharb Easy: if we can make special syntax for things like |
OK, so then we have What is |
That's simply a matter of taste, that the community can decide on. In my opinion, these should all work: private.foo
// same as
private(this).foo
protected.foo
// same as
protected(this).foo
static(this).foo with other objects, the only form being: protected(otherObject).foo
static(otherObject).foo // if constructor is not private. which is perfectly fine as far as my taste goes. And static: static(private).foo
// same as
private(static).foo but community taste could decide to limit it only to private(this).foo
protected(this).foo Another possibility is to treat the words like (private this).foo
// not the same as
(private this.other).foo // accesses private of the 'foo' reference, more confusing.
(private otherObject).foo Treating it like I'm just pointing out that we can decide which one we like, and that they are doable. Yet another option is to treat it in a new way unlike any other keyword, not like private this.foo
private (this.other).foo // access private scope of .foo reference
private otherObject.foo All of those are doable, but I think the first one is the best of these, and is the most intuitive treating it like and object, or a function for accessing on another object. It will be easy for people to learn that, and more aesthetic than When running Class('Foo', (public, protected, private) => {
method() { console.log( private(this).foo ) }
private: { foo: 'foo' }
}) |
Anything's possible, f.e. we could let hard privacy be opt-out: class Foo {
getPrivates() {
return private
}
private foo = 'foo'
private bar = 'bar'
}
// assuming it's just an encapsulated object (until leaked on purpose):
for (const key in (new Foo).getPrivates())
console.log(key)
// output:
// foo
// bar |
private.private // access the private instance variable named "private" Also, protected and private should work only in strict mode, where The moment we define a |
This would better align with how private members would be implemented with WeakMaps. |
@thysultan only if you had one WeakMap per class - not if you had one per field, which is basically the current desugaring. |
|
In real implementations, it should be possible to use private fields without having a separate, per-instance allocation. The problem with having If we use |
Having one WeakMap makes more sense memory wise, right? Why would you want 500 new objects if you have 500 private properties? |
I like It'd be nice to maintain the dynamic nature of JS! Here's another idea: private.foo
// short for
this#private.foo
protected.foo
// short for
this#protected.foo
static.foo
// short for
this#static.foo
obj#private.foo // get private prop of other object, no short form. |
How about that? Does that satisfy the non-functional-like requirement while still allowing spec flexibility for possibly adding |
Here's another idea, if we're worried about GC and making extra objects for private parts, what if we return f.e. // returns a Proxy-like thing, lazily, otherwise that thing doesn't exist before this point.
const privates = private(obj) or // returns a Proxy-like thing, lazily, otherwise that thing doesn't exist before this point.
const privates = this#private It's just a lazily-created shell that wraps the same internal native mechanism by which private (or protected) fields are accessed, accessing them the same way but providing an interface that can be passed around by reference to external code. |
@trusktr I sent a mail to you. |
Can we just have a private and public keyword as in every other language? Also just call the method without a prefix. That makes it easier to refractor later on. |
@gvlekke Unfortunately, due to JavaScript's dynamic typing, we can't go without some special syntax at the usage site, in addition to the definition site. See the FAQ in this repo for details. |
If this is strictly a syntax change then it should work so long as You could define private properties to be akin to a single weakmap per instance, keeping the dynamic nature and compatibility with property operators (these are NOT compatible with the current proposal). But that behavior is intentionally avoided in this proposal (would like to understand more why). |
Since private properties are statically known, what do you need dynamic access for? ( |
Apologies for what I'm sure is a duplicate suggestion, but I failed to find it in the other long threads or the FAQ. (I have read this thread from 2015 but it seems to be worried about cases that don't apply to the version that's actually shipping in 2018, such as resolution in lexical scopes other than directly inside class blocks and methods.) Why isn't it viable to implement When we're reading or writing a property, the descriptors already (conceptually) need to be checked to see if they're writable or have getters. Additional complexity here is regrettable, but since it's just (edited:) If these descriptors are non-enumerable and non-configurable, I think the values would be hard-private as required. In terms of declaration, |
@jeremyBanks it’s definitely a design requirement of some (a tradeoff for others) that private fields be “hard private” - in other words, not possible to even observe the existence of, let alone circumvent or manipulate from outside the class. |
@ljharb Understood, thanks, I missed #33. However, if these descriptors are non-enumerable and non-configurable, I think that woulds make the values hard-private. The fact that private fields exist on the class woulds not be a secret, but I don't think most languages try to hide that, since that's a static property of the code. |
@jeremyBanks they don’t; but it’s a requirement for some (myself included) that even the existence of the keys, let alone their names, is impossible to detect (modulo function toString). |
Besides what @ljharb said, I think it's important that it be possible for a derived class to have a public field which shares its name with a private field in its base class. (See this FAQ entry.) That doesn't work if it uses a property descriptor. |
I meant, instead of making private properties dynamic and thus compatible
with property behavior today (OP's suggestion), this proposal chose to
define them otherwise.
But yes, if you intentionally define private properties to behave
statically then indeed you have no need for dynamic behavior. The question
is more of why one vs the other.
…On Monday, December 17, 2018, Nicolò Ribaudo ***@***.***> wrote:
Since private properties are statically known, what do you need dynamic
access for? (private(this)["foo"] vs this.#foo)?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#177 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADCi0dWNxQqNmx4VbLv7rSnhjBcGcMMxks5u59LugaJpZM4ZJzyu>
.
|
Something I think many in the community are failing to grasp is that the private field #foo is not a property named "#foo" on the object marked private. I don't know how I'd go about driving that point further, but it's just something I've noticed. I also think it would be beneficial to rename the "Private Fields" feature to "Encapsulated Fields" to address a few concerns:
|
I grasp it, and that's one of the reasons I dislike it, because we will (as currently spec'd) not be able to take advantage of a huge collection of community tools with respect to working with those properties (f.e. |
In your 2nd point @superamadeus,
and as @littledan mentioned, alternative words could totally be a thing. I'm okay with it. However I'm not sure that the private and protected ideas here actually deviate enough from other languages. They're only slightly different due to implementation and how the language currently works. Scratchpad: class Foo {
protected foo = 1
private foo = 2
}
class Foo {
hidden foo = 1 // protected
internal foo = 2 // private
}
class Foo {
shared foo = 1 // protected
hidden foo = 2 // private
}
class Foo {
around foo = 1 // protected
here foo = 2 // private
}
class Foo {
shared foo = 1 // protected
here foo = 2 // private
}
class Foo {
shared foo = 1 // protected
here foo = 2 // private
}
class Foo {
sub foo = 1 // protected, "sub" because it can be inherited by subclasses
local foo = 2 // private
}
class Foo {
inherited foo = 1 // protected
local foo = 2 // private
}
class Foo {
inheritable foo = 1 // protected
local foo = 2 // private
}
class Foo {
family foo = 1 // protected
local foo = 2 // private
} Hmmmm... what about those last ones, with |
Hmmmm... this seems to open up possibilities: class Foo {
family foo = 1
local foo = 2
logFoo() {
console.log(family.foo) // 1
console.log(local.foo) // 2
}
}
class Bar extends Foo {
inherited foo; // explicitly inherits the "protected"-like foo prop from Foo
local foo = 3
logFoo() {
super.logFoo()
console.log(inherited.foo) // 1
console.log(local.foo) // 3
console.log(family.foo) // undefined
}
}
class Lorem extends Bar {
family foo = 4 // override the inheritable family var from Foo
logFoo() {
super.logFoo()
console.log(inherited.foo) // undefined
console.log(family.foo) // 4
}
}
class Ipsum extends Lorem {
inherited foo; // explicitly inherits the "protected"-like foo prop from Lorem
logFoo() {
super.logFoo()
console.log(inherited.foo) // 4
console.log(local.foo) // undefined (hard privacy)
console.log(family.foo) // undefined, this class has not defined an inheritable family foo var
}
}
const i = new Ipsum
i.logFoo()
// Output:
// 1
// 2
// 1
// 3
// undefined
// undefined
// 4
// 4
// undefined
// undefined
Although this is more verbose to write than |
If we use |
scratchpad: Keeping it dynamic: _.pick(this, 'a', 'b', 'c', private) // pass the access mode helper
//
_.pick = function(source, ...args) {
let accessHelper
if ( Object.isAccessHelper( args[args.length-1] ) )
accessHelper = args.splice(args.length-1, 1)
let result = {}
for (const prop of args) {
result[prop] = accessHelper
? accessHelper(source)[prop] // indexed access!
: source[prop] // yay!
}
return result
} Or with _.pick(this, 'a', 'b', 'c', #) // pass the access mode helper What are class Foo {
test() {
console.log(typeof private) // 'accesshelper' or something?
console.log(typeof #) // 'accesshelper' or something?
}
}
console.log(typeof private) // SyntaxError, use of "private" is unexpected here
console.log(typeof #) // SyntaxError, use of "#" is unexpected here
// or perhaps
console.log(typeof private) // 'undefined', I like this better
console.log(typeof #) // 'undefined' |
Please no! That ruins the whole point. This is one of the reasons why I don't quite like the concept of private symbols as is. The fact that it is possible to leak the keys makes it too easy to make a mistake. Here, you're leaking the not the keys, but the entire mechanism! I get that you want to add dynamism to private access, but thats's just not a good way to do it. If you want to see how it can be done, look at proposal-class-members. It makes use of private symbols, but gets rid of the leak issue. It also supports dynamic access to private members: |
Isn't that actually better, because in the example of |
Plus, I want to be able to implement things like "package protected" or similar concepts, and we need to be able to willfully leak these things in order to do it. |
Would |
class Example
let a = 1;
let b = "fu";
let c = Symbol("42");
let access = (key) => this::[key];
getSubset() {
return _.pick(this, 'a', 'b', this::access);
}
} This way, the mechanism isn't hidden and access is under the complete control of the developer without being subject to any accidental leaking. |
Package protected? I'm assuming you mean shared between packages. But other than |
Based on the ideas of #195 but with the keyword syntax from above, no new object is created, but only the access mode is modified. Enumeration of a "property" is based first on whether the access mode allowed the descriptor to be visible. Then, the descriptor It could be possible that
That obviously wouldn't be possible. In the case of class Foo {
constructor() {
Object.defineProperty(this, 'foo', { ... }, private)
}
} But if we're allowed to save an object reference in a certain "access mode" like in #195, then the following would just work without changing the implementation: class Foo {
constructor() {
Object.defineProperty(private(this), 'foo', { ... })
}
} |
This is true, but it's no worse than "public" that we have today. // you must trust the library that you use in your application:
import _ from 'underscore'
_.pick(this, 'x', 'y', 'z', private)
// or you can copy/paste it from underscore's source into your project
// to ensure it is the version that you expect |
The goal of private fields imo isn't to be "no worse" than public, it's to be much more encapsulated than public. |
Given how easily objects can be unintentionally exposed (ie, |
The engine knows in which scope a method is called, and on which receiver. It is then possible that the engine can throw an error in the case of _.debounce(private(this).someMethod, 100) when That API would also need an upgrade, so that things are explicit: _.debounce(private(this).someMethod, 100, {target: this, accessHelper: private}) and internally maybe it would need to use some new API like _.debounce = function(fn, time, options) {
// ...
fn.applyAccessed(options.target, options.accessHelper, args)
// or
fn.callAccessed(options.target, options.accessHelper, ...args)
// ...
} then I don't see how |
Unless of course EDIT: hmmm, or maybe not. F.e. like EDIT 2: but if it is based on lexical scope, then maybe an arrow function does work. I suppose this is a matter of deciding which we prefer. I'd rather not prevent a feature just because "someone might accidentally break privacy by passing both an object and a helper to outside code". Currently someone might break privacy by passing anything to outside code. We just don't do that when we shouldn't. At the same time being able to do it would be a powerful tool. |
A concept like "module protected" or "module private" is easy: just leak the helper or object-with-protected-mode inside the module scope, similar to the example in #195 (comment) where the private properties of one class are shared with another class in the same module on purpose. For "package private" or "library private" where private fields can be shared with a class outside of a module, the first idea I came up with was that we could build on @bmeck's "gateway pattern", mentioned here, to create code that tracks how many times an import is used during module evaluation time (with function calls that increment a counter, called once per module). If we strategically know how many of our modules will import the private class, then if the module is imported more times that we expect we can throw a helpful error to tell the user of the library "hey, don't do that", and we can also put the code into an unrecoverable state when that happens in order to force the user not to do that (unless they want to fork the code). A library dev could add a module to the library/package and break the code, but if they're testing in dev mode they'll see the error hopefully. A test file could run on commit hook which does a sanity import check. |
Seems like you're looking for something similar to the concept of a namespace. In ES, it's the much maligned global object. To make for something module-protected, your module would just need to put up a global class. Anyone choosing to extend it would gain access to its protected API. "Package/library private" that can be shared is not private at all. That's "protected" again. Private things cannot be seen outside of their container. |
Make |
@MichaelTheriot There would be other objections to that, as it looks like an expression. I added an FAQ entry about the |
Not quite, because I can make subclasses of my own base classes inside my own modules, and decide to expose only a factory function to the public, while the internal class hierarchies can share their protected members, yet the public will not be able to gain access to them even via extension because I will have deleted the This would not be like a global namespace with classes containing protected members. Besides, writing protected members in another class outside of my classes would just make the class code harder to understand, having to jump from one class (in my lib) to the other class (the on on the global) to read code that should all just be in a single class. |
Yes 👍, except inside a class if |
Anything like |
Honestly I don't think something like |
Insufficient. If you're using
Tell that to the Google Dart team. They need to hear it. 😜 |
I can delete Some sort of mix of ideas from #205, #206, and #195 is starting to be appealing. Any ideas in that possible direction? What I think this spec is missing is an explainer on ideas for future add-ons that can address missing features. I made an issue for it: #212 |
Besides being ugly and not true to JavaScript's historically semantic verbosity (f.e. like
async
/await
is), it has the technical limitations mentioned in #90 (comment) and #195 (comment).Can we please move the syntax (and whatever else needed) back to stage 1 or even 0?
The syntax ideas presented in #90 (and similar suggestions) are well aligned with other semantic features of JavaScript.
Just like
async
/await
in JavaScript is clearly articulated when you see it in code compared to other languages that have coroutines, having explicitprivate.foo
style accesses is clean, clear, semantic, not ugly, and more familiar to languages that have similar keywords.Let's not make JavaScript ugly, let's keep it beautiful.
The text was updated successfully, but these errors were encountered: