-
Notifications
You must be signed in to change notification settings - Fork 3
My 2 cents... #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks, @rdking, for your review. In some ways, I think this would be really interesting, syntactically: (Taken from an example on the private methods repo.) class Counter extends HTMLElement {
const setX = (value) => { x = value; window.requestAnimationFrame(render); };
const render = () => this.textContent = x.toString();
const clicked = () => setX(x++);
let x = 0;
constructor() {
super();
this.onclick = clicked;
}
connectedCallback() {
render();
}
static {
window.customElements.define('num-counter', this);
}
} (Note use of shorthand, |
@zenparsing I agree, this looks familiar enough that I understand what is going on without much thought. I know I'm having another conversation over at tc39/proposal-class-fields#147 but really either one of these two approaches seem so much more like JS to me. If |
I find myself leaning toward |
@rdking's proposal has |
@shannon Actually it was I haven't given it much thought yet, but while this definitely solves the need for private data (presumably by making a closure around an instance), how does this leave room for selective sharing of the private variables with derived classes? As I've said before, it won't take long after the addition of private fields for there to be a need to share private data in a limited way. Even if it's going to be an additional proposal, it needs to be accounted for in any proposal that gets implemented |
@rdking I commented for @zenparsing gave a interesting idea that, using Actually, I also have a new idea close to it recent days. One flaw of the shorthand syntax is, it only apply to instance variable, not hidden method, because you always need But, hidden methods could almost only useful in its own class. Call it with other receiver just throw TypeError (though in theory, it doesn't do branding check so won't throw if not access instance var). So why we need So mostly you are just using implicit Example: import calcHash from 'util'
class X {
my x
constructor(x) {
x@this = x
}
my hash() {
return calcHash(x)
}
equal(other) {
return hash() === hash@other() && x === x@other
}
} I just unified Any comment? |
Yea sorry that's what I meant to type. |
I agree that calling "private methods" with Syntax-wise, I think we lost Also, I worry that users would see a private "method-like" syntax: class X {
my m() {}
} and because it looks so similar to a regular method, they might be confused that they cannot access it with The thing that I like about In order to make the shorthands work we would have to do a bunch of work on of "Function Environment Records", but it's possible (at least from a spec point of view). |
I'm sure it's been brought up before, but what if we make var fn = (function() {
var x=1;
var retval = function Example() {
console.log(++x);
};
retval.doSomething(obj) {
//It's impossible to reach the x that belong to the closure of obj
}
return retval;
})();
var a = new fn();
fn.doSomething(a); I'm thinking that import calcHash from 'util'
class X {
let x;
let hash = () => calcHash(x);
constructor(x) {
//Must use this::x instead of just x due to shadowing by fn arg.
this::x = x;
}
equal(other) {
return hash() === other::hash() && x === other::x;
}
} |
Forgot to mention that this operator only works in a function on objects toting around a closure from the same function. |
Here are some notes about how I think it could work. The idea is to literally use the closure pattern. No WeakMap-like things behind the scenes. Conceptually, in addition to keyed properties, all objects contain a possibly empty list of instance environment lexical environments, keyed by a symbol.
Each function object contains an internal slot named
When an object is constructed, if the constructor has an The abstract operations for function environment records are modified such that:
Instance variable member expressions of the form
|
Is there any syntax conflict with
Yeah, |
@hax Would you consider |
@zenparsing Wouldn't requiring a |
@rdking Generally, for static methods I think you'd need the |
@zenparsing Wouldn't that still affect the operations of the function environment record with regard to binding? |
I can't say |
@hax it just occurred to me. If an instance of a |
@zenparsing, do you think that's worth the cost of making variable scope even more complicated? To me it seems like "block scope vs function scope" is hard enough to teach without introducing a third kind of scope which is based on runtime values rather than syntax. |
@bakkot The idea here is that an instance carries around a function scope that the member functions can all have access. It would basically have writing this: class Example {
let private1 = "something";
function privateMethod() { //Do something here }
method1() {
console.log(private1);
this::privateMethod();
}
method2(other) {
other::privateMethod();
}
} turn into something much like this: var Example = (function() {
function Example() {
if (!new.target) { throw new TypeError(`Constructor Example requires 'new'`);
let private1 = "something";
function privateMethod() { //Do something here }
Object.setPrototypeOf(this, {
method1() {
console.log(private1);
privateMethod.bind(this)()
},
method2(other) {
privateMethod.bind(other)()
},
Object.getPrototypeOf(this);
});
//Back to your regularly scheduled construction
}
Example.prototype = {
constructor: Example,
method1() { throw new TypeError('Invalid scope access'); },
method2() { throw new TypeError('Invalid scope access'); }
};
return Example;
})(); There's no equivalent in ES6 for private variable access among siblings unless those siblings share a container holding their private data (like a WeakMap). The approach taken here is that a function having access to a closure from a given function can use the |
@rdking It is not like that. I am interested to hear @zenparsing's thoughts. |
@bakkot I wasn't trying to present an exact equivalent, because there is none. You're right about the methods. I was only trying to show that the methods had access to the function's scope as if they had been created there. Has variable scoping really been that hard to teach? I would've expected that something that's in C, C++, Java, PHP, & C# (and several other languages) would be fairly easy to teach. |
@bakkot It's a bit off-kilter, but I like to explore ideas like that sometimes. I don't have a ton of confidence that I'd be able to convince anyone to implement these semantics. I'm also unsure about what an implementation would look like, although it seems like you can statically determine which bindings need to be looked up in the Regarding mental model complexities, I think there are complexities one way or another regardless of what we do. I tend to be attracted to solutions that build on existing knowledge and patterns (like closure-for-private or "private symbols"). A large part of me would prefer that we do nothing (not even public fields) in order to minimize risk and future constraints. |
@zenparsing yeah, I don't think the implementation would be hard, just teaching it.
I think that's a very good design principle, but I also think it's very important that new bits of complexity not spill out into unrelated parts of the language. If you have to know that there's special scoping rules for variable references in class bodies, that spills out into everywhere you have to know about scoping rules. (Similarly, if you have to know that private symbols don't trigger proxy traps and don't show up in These can easily be in tension - if your solution builds on an existing pattern by adding complexity to code which makes use of semantics related to that pattern, or by changing other parts of the language so that you can use code which looks like that pattern even though it needs special support to work, that will almost necessarily mean you're adding complexity to parts of the language unrelated to the problem you're actually trying to solve. |
@bakkot Sure. All of that applies in spades to the current Stage 3 solution as well, though. I hope I was clear about that in my presentation regarding private symbols. Also, I don't need a lecture about PL design ; ) |
@zenparsing Sorry, didn't mean to lecture! I'm just trying to sketch out where our meta-level disagreement seems to be. (Object-level, I also disagree that whatever complexities there are in the current stage 3 solution spill out into the rest of the language. The fact that this is not the case (from my perspective) is one of the things I like most about it. I hope that was clear in my response to your presentation regarding private symbols!) |
How do we get out of this zero-sum game where one side "wins" by refusing to acknowledge the other side's point of view? |
@bakkot BTW, I think you bring a lot of PL talent to the committee and do wonderful work. 👍 |
@zenparsing I think a lot of this comes down to actual disagreements, not refusal to acknowledge points of view. I don't know how to resolve disagreements other than talking for a long time, though. Having tried that, I don't really know what else to do. Some disagreements seem unavoidably zero-sum, like the reflection vs encapsulation debate. |
Actually, if consider the average JS programmers, I am sure classes 1.1 proposal is much easy to learn compare to current proposal. I already discussed the possible teaching issues with some educators in the China JS community. They all agree a proposal like classes 1.1 is much simple to learn for average JS programmers. The key point is, for average users, they need to learn not only syntax, but also semantic. I do not have enough time now because we have a three days tech conferences from today, so I just throw the point, if you still not catch my point, I'd like to write a full explanation about it later. |
The trouble might be a case of implicit bias. Honestly, you guys have been trying to tackle this issue for so long that it's perfectly reasonable to think you've got the best possible solution, especially since its been implemented in various engines and tools to reviews that aren't failures.... and that's just for your side. Then there are those of us who very obviously have different priorities than you for this feature. So there's a lot of bias to go around. One way to resolve the disagreements is to try analyzing the situation from as many perspectives other than your own as possible. Sometimes the problem looks very different from a different angle. Also try sticking as close to logic as possible to weed out any unnecessary emotional bias. Not saying that emotional bias is entirely unneeded, but in many cases it just complicates things. For instance, those cases where you say you value X more than Y are indefensible. They don't make for resolvable argument, and when used to defend against arguments that have a rational or logical basis, it comes off as being dismissive. Instead of falling back on that kind of defense, try explaining why you value X more than Y to see if there's any chance the other party may also think your standpoint has a higher priority, or maybe the other party has a compromise solution for the problem type you describe. You'll never know until you present the argument. And I get that doing this repeatedly feels like banging your head against a brick wall. However, if we're to get to the other side of this with a feature we're mostly at least indifferent about and willing to use, then that kind of pain is necessary. |
Easy, let classes 1.1 forward to at least stage 0, so we can have Babel plugin. And let the community try, experiment, compare and decide. |
FWIW, the syntax above isn't so different from Swift. https://docs.swift.org/swift-book/LanguageGuide/Methods.html |
@hax @bakkot @littledan
And you can't forget the possibility of multiple responses to a single question. |
With regard to considerations for TypeScript(TS)...
While I can empathize with this point of view, I have no sympathy for TS users. Nor do I believe they need any. Consider some 30 years ago when C++ was being developed as a macro pre-processor on top of a C compiler in much the same way as TS is "compiled" into JS. The development of the two languages has grown to the point where now C and C++ have long since crossed the line where a valid C program is not necessarily a valid C++ program. I believe the same thing will eventually be true for ES and TS. So I simply do not see any good reason for the development of ES syntax to be constrained by its potential effects on TS. This is not the first time one language has grown and forked from another, and I seriously doubt it will be the last.
With regard to
var x;
in aclass
..."I puzzed and I puzzed 'till my puzzler was sore." Then I thought
let
would be a much better fit thanvar
given the intended meaning. Withlet
it becomes perfectly clear that the surrounding{}
is the scope for the variable being defined. This is even more true when you consider that the contents of aclass
definition is a prototype description. This means that, at least for me, the visual expectation was that theclass
definition, being a non-function, would not be able to constrain the scope for avar
. Withlet
, however, all that's needed is a set of{}
to constrain the scope.With regard to
this->x
...I find the potential to mistype
=>
when one means->
somewhat disturbing. The good news here is that on the occasion where=>
doesn't produce an immediate error, it will cause the code to do something obviously weird. That makes the problem somewhat easier to find than would be the case for a missing sigil from other proposals. Still, I think I like::
better for this use. And yet there is still a problem.Neither
var
norlet
imply to the user that an object must be consulted to access the variables defined by that keyword. In fact, the natural implication of using those keywords is that within the scope, the corresponding variable can be reached simply by referencing it directly (var x;
/x+=2;
). To require a context object for accessing such a declaration is a definite break from the way they are currently used. For me, this falls back on why I cannot sympathize with TS.With regard to
hidden
...First,
hidden
would for me imply that whatever it tags is not enumerable, not that what it tags is not accessible from outside theclass
scope. No matter how I look at it, the word for that isprivate
. But I understand the reasoning. I just don't buy that this is the correct term for the job. Second, the use ofhidden
seems inconsistent. As I stated before,'var
doesn't imply that the variable being defined has any instance connection. At leasthidden
implies that something is doing the hiding, and thus there must be an object through which to access it. So even in this case, even though it feels like the wrong word, it also feels like it should be used consistently for things owned by an instance object but not accessible publicly.Beyond these 4 issues, I like this proposal more than the class-fields proposal. At the same time, I would like to see some information on the expected implementation. The syntax seems to say most of the right things, but if that's not backed by the right implementation strategy, this proposal could potentially be even more bothersome than class-fields.
If one of the goals really is to have a complete proposal for
class
that requires no future modification, well, you missed the mark, and not for the reasons above either, but it's a good start. I also like that the need for a static constructor/initializer wasn't forgotten. If I had to choose between going forward with the class-fields proposal, or this one, I would definitely prefer this one.The text was updated successfully, but these errors were encountered: