Skip to content
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

How to critique this proposal #32

Open
allenwb opened this issue Mar 12, 2018 · 68 comments
Open

How to critique this proposal #32

allenwb opened this issue Mar 12, 2018 · 68 comments

Comments

@allenwb
Copy link
Collaborator

allenwb commented Mar 12, 2018

When reading and raising issues about the Max-Min Classes 1.1 proposal it is important to understand how it relates to the current set of class feature proposal that are at various states in the TC39 process pipeline.

This proposal is a response to the overall complexity that would be introduced into the language by the full collection of current and contemplated proposals. Basically, we don't think that the JS complexity budget for the near future is big enough to invest so much of it into class extensions. JavaScript has generally followed a minimalist design style. From that perspective, the contributors of this proposals are primarily concerned about the amount of complexity that the existing proposal would inject into the language. The complexity concern has also been raised by a number of community members.

This proposal is not a point-by-point attack of any feature of any of the current proposals. There are many good ideas in the existing proposals. Some of them we've borrowed. Instead, this proposal is an attempt to fight back against overall complexity. We are not trying to "fix" or improve other any existing proposal. It is an experiment to see where we might end up if we start with a clean slate and took a minimalist approach to adding some pretty essential functionality that is not in ES6 class definitions. We want to see where we get if following that same Max-Min methodology that enabled the introduction of class definition syntax into ES6.

This proposal is a carefully balanced set of functionality that attempts to include just enough and not too much new capability and associated new complexity. The best way to critique this proposal or understand the rationale for our decisions is to focus on the actual proposal rather than trying to do a feature-by-features bake-off against other proposals. For example, rather than asking "why did you leave out public fields?" ask "Why didn't you include a mechanism for declaring instance own properties?"

We fully expect to have to explain difference between various features of this proposal and other proposals. Just remember, that sort of point-by-point comparison is not the design methodology we have followed and may not give you the best understanding of this proposal. Try to understand it as a whole before you start the feature point comparison.

@bakkot
Copy link

bakkot commented Mar 12, 2018

Instead, this proposal is an attempt to fight back against overall complexity.

I definitely feel the complexity budget squeeze as the language continues growing. But... I guess I really don't share the impression that this proposal uses less of it than the existing one. It adds four new and diverse syntactical forms, one of which overloads an existing one with radically different semantics: var x, hidden foo(){} this->x, and static {}. And it fails to provide for what I think is the overwhelming majority use case, which is initializing public instance & static fields. It feels like spending more and getting less.

I agree that the important thing is to consider proposals holistically, with a particular emphasis on overall complexity. I am just confused by the implication that this proposal is less complex that the current one. To me it feels far more so.

@zenparsing
Copy link
Owner

I guess I really don't share the impression that this proposal uses less of it than the existing one.

As @allenwb states, the comparison should not be to any single proposal in isolation, but to the full set of class features that are either in some form of development or have been deferred to future proposals. That is a large list, with noticeable unresolved problems. We see our alternative as a small, closed, self-sufficient set of essential functionality.

Of course it's not perfect (yet?), but that's not the point. The point is to see if we can create an alternative that is complete (and therefore represents less design risk), while avoiding some of the troubles that the current proposals find themselves in.

@ljharb
Copy link

ljharb commented Mar 13, 2018

One conflict here is that some of the use cases this "minimal" proposal doesn't address, are indeed essential. The complexity that the current fields proposals add tends to be deemed "very much worth it" by those who need these use cases.

If a less complex proposal can address them, then great! However, if not, then it's not actually a better proposal - it's just a simpler one, which isn't inherently better in and of itself, despite the value of minimizing complexity.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 13, 2018

@ljharb

some of the use cases this "minimal" proposal doesn't address, are indeed essential.

I guess it depends upon your definition of "essential". The definition I use is provided in #11

But from a Max-min design approach, the meaning of essential is fairly straight forward. If all of the stake holders in the design agree that something is essential, then it is essential. If there is disagreement as to whether or not some feature or capability is essential, then for the purposes of the Max-min design it can't be considered essential.

As a starting point on what is essential, I this we have significant consensus that some mechanism that provides securely encapsulated per instance state is essential.

@ljharb
Copy link

ljharb commented Mar 13, 2018

Max-min motivated the ES2015 design, and while that may very well have been the right decision, I'm not sure that was in fact considered "successful" nor that max-min is automatically the right approach to take now that we have the post-ES6 process.

@hax
Copy link
Contributor

hax commented Mar 13, 2018

This proposal is great!

It avoid the main syntax issues in current proposals:

  1. No #priv which most programmers hate. (Yes, they hate it.)
  2. No new ASI hazard. (by reuse var keyword)

It also make the internal slot (instance variable) concept much much clear by introduce -> diff from . or [].

I agree public property initialization is good to have, but not must to have in current stage. We could leave it, check how TypeScript users use the features here proposed and property initialization they supported together, then decide whether or how to introduce public property initialization in the future.

I LOVE this proposal! Thank you @zenparsing and @allenwb to bring this to us!

@littledan
Copy link
Collaborator

Note that it's the combination of reusing the var keyword and not having any initializers that make this proposal eliminate ASI hazards.

@hax
Copy link
Contributor

hax commented Mar 13, 2018

@littledan I think even having initializers it will make much less new ASI hazards than current proposals.

class A {
  var x = 0
  *gen() {}  // syntax error
  var y = 1
  [computed]() {} // syntax error, and it's not a new ASI hazard for semicolon-less coding style
}

All other weird cases are just gone.

@bakkot
Copy link

bakkot commented Mar 13, 2018

@zenparsing,

but to the full set of class features that are either in some form of development or have been deferred to future proposals. That is a large list, with noticeable unresolved problems

This confuses me a bit; can you be more explicit about what you're referring to?

@hax
Copy link
Contributor

hax commented Mar 13, 2018

@bakkot

one of which overloads an existing one with radically different semantics: var x, hidden foo(){} this->x, and static {}.

  • var x: I think this syntax is very easy to understand, the old var is for function local variables, and this is for class "local" variables.
  • this->x: At least much better than #x
  • static {}: Easy to understand for Java/C# programmers. (Though C# use a little different syntax.) It seems current proposals do not have corresponding feature. As the examples showed, I see it's a useful feature, but as I understand it's not the core feature of this proposal and can be postponed, I can live without it.
  • hidden foo(){}: I wish it could be private foo() {}, but it rely on TypeScript team's decision of how to incorporate their private and js native private/hidden. So I'm fine with hidden, we already introduce keywords like yield/async/await, so it's not a big deal. Again, at least much better than #foo() {} for most programmers.

@bakkot
Copy link

bakkot commented Mar 13, 2018

@hax:

var x: I think this syntax is very easy to understand, the old var is for function local variables, and this is for class "local" variables.

It's not a class local variable, since a function which closes over it will see different values depending on how the function is invoked, which breaks a fundamental contract of variables in JavaScript. It's not a variable at all, in any sense. Short of eval, there's no place in the language where you can summon a new variable into being without creating a new function. It's a value associated with an object. I get why it's confusing to call it a property, but I think it's more confusing to think of it as a variable.

[etc]

I'm not saying any of these are necessarily bad, except the first. I'm just saying they collectively add a huge amount of complexity to the language. I stand by that claim.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 13, 2018

I'm not saying any of these are necessarily bad, except the first. I'm just saying they collectively add a huge amount of complexity to the language. I stand by that claim.

Of course this proposal adds complexity to the language. If it didn't add some complexity it would be adding any new expressibility to the language. What it tries to avoid in adding inessential complexity.

You aren't arguing that this proposal adds more complexity then then union of all the class extensions proposal that are already in the TC39 pipeline, are you?

@bakkot
Copy link

bakkot commented Mar 13, 2018

@allenwb,

You aren't arguing that this proposal adds more complexity then then union of all the class extensions proposal that are already in the TC39 pipeline, are you?

I can't answer this without knowing which things we're considering to be class extensions proposals. The class fields proposal, sure, but... Callable constructors? Decorators? Mixins? A future shorthand for private field access? I don't see how this proposal would rule any of those out, for example, and commentary elsewhere suggests it's not intended to. But as such I don't understand why we'd be interested in comparing the complexity cost of "this proposal" vs "the current class fields proposal, plus callable constructors, plus decorators, plus mixins, plus shorthand".

I do claim it adds more complexity that the current class fields proposal. I personally would be more than content to get just that proposal and no others, so from a max-min perspective, it seems like that proposal would be a strict improvement over this one.

@zenparsing
Copy link
Owner

I personally would be more than content to get just that proposal and no others, so from a max-min perspective, it seems like that proposal would be a strict improvement over this one.

Let's look at a specific use case: self-hosting builtins with class definitions. The fields proposal is not sufficient in its current form to completely address that use case. It is missing the capability to (a) securely decompose methods and (b) expose access to encapsulated state outside of the class for use by "friends". If we want to take a holistic view of class features, then we must also take into account the additional proposals that would support these capabilities, along with any uncertainty or risk surrounding those features.

@bakkot
Copy link

bakkot commented Mar 13, 2018

The fields proposal is not sufficient in its current form to completely address that use case. It is missing the capability to (a) securely decompose methods and (b) expose access to encapsulated state outside of the class for use by "friends".

(a) Sure. Then let's consider as our point of comparison the class fields proposal + the private methods proposal, which is incidentally the set of stage 3 proposals touching classes.

(Although, of course, using per-instance private fields containing functions amounts to the same thing; it creates more objects, but provides the capability desired with nothing beyond the class fields proposal.)

(b) This doesn't require language level support.

let subscriptionClosed;

class Subscription {
  #state;
  static finalize() {
    subscriptionClosed = s => s.#state = 'closed';
    delete this.finalize;
  }
}
Subscription.finalize();

I don't claim it's clean, just that it's doable, and concisely.

Furthermore, if we got decorators it could be done more cleanly than this. I don't think this proposal can be considered in isolation from other proposals such as that one.

then we must also take into account the additional proposals that would support these capabilities, along with any uncertainty or risk surrounding those features.

To my eye, it looks like it requires only the class fields proposal plus the private methods proposal. The union of those two still seems much simpler than this, while providing more. Do you think there are others which are necessary for this use case, or some other specific use case? Which?

I'm sorry to push on these details so hard, but I really feel like we can't much talk about this without being explicit about what you and Allen mean by "the full set of class features that are either in some form of development or have been deferred to future proposals", and which of those you feel this proposal subsumes or renders unnecessary. As long as it's not "all proposals touching classes", which it seems it is not, then "the full set" isn't really specific enough for me to tell what you're intending to point at.

@hax
Copy link
Contributor

hax commented Mar 13, 2018

@bakkot

which breaks a fundamental contract of variables in JavaScript

Sorry I don't get this. Could you give a small example show the breaking contract?

It's not a variable at all, in any sense... I think it's more confusing to think of it as a variable.

Of coz it's not function variable, but I don't see why "instance variable" is wrong. We already use the term "instance variable" several years in many languages and loosely in JavaScript. For example DC's very old article: https://crockford.com/javascript/private.html already use "instance variable".

@zenparsing
Copy link
Owner

I claim that the delete trick for exposing private access is not sufficient, in the sense that it is not usable enough on its own to leave it as-is. It creates a void into which some kind of syntax must find its way. If that syntax is decorators, then we must add the decorators proposal to the "complete" class feature set.

Furthermore, there are some issues with those proposals. From a developer's point of view, does it make sense that you can have static fields and static methods but not static private? Why is a private field just a value slot, but a private method has property-descriptor-like semantics (e.g. allowing accessors)? Is exposing something like PrivateName through decorators the safest way to expose private access? Will developers get over their distaste for #?

The list above isn't meant to be a point-by-point critique of the current set of proposals. Rather, it is meant to call attention to the fact that the current direction for classes seems to imply a fairly high degree of complexity and risk.

Part of the problem is that the current approach to developing class features is so open-ended that it's hard to tell what classes will end up looking like, or what features will end up in them. Narrowing our scope helps give us confidence.

Also, we understand the disruptive nature of this proposal, and I really appreciate the feedback!

@littledan
Copy link
Collaborator

I agree that we shouldn't encourage anyone to use the delete trick. I think we should work on a static blocks proposal for this purpose. Several TC39 members are talking about this and I hope we'll see a Stage 1/2 proposal soon.

@bakkot
Copy link

bakkot commented Mar 13, 2018

@hax

Sorry I don't get this. Could you give a small example show the breaking contract?

Sure, let me elaborate. JavaScript functions are lexically scoped: the variable to which a given identifier refers is determined at the time the function is defined, not the time at which it's invoked. (It's a little more complicated than that because of dynamic scope like with [in fact it's the scope chain which is fixed, not the reference resolution], but bear with me.) Some functions introduce their own variables in the form of parameters and this, but these aren't captured from an outside context, and references to these do not conceptually "leave" the function.

That means that if I have

var x;
[...]
function f(){
  [...] x [...]
}

then no matter what I do with f or how it's referring to x, that x is always the same x. It can only hold a single value: if I mutate x that change is reflected in subsequent invocations of f; I can't invoke f it in a way which changes that. This is true no matter what kind of function f is: in particular it is true for methods.

With this proposal's var x, that's not true:

class A {
  var x;
  f(){
    return this->x;
  }
}

This creates only a single function f (and some others, but they're not relevant). But the thing which x refers to there is not a lexically captured variable in any sense. There are potentially infinitely many values which can be stored in it simultaneously, and which of them you get depends on how f is invoked: calling it as a method of one object can yield a different value than calling it as another. That's what I mean.

To be more explicit:

var x = 0;
class A {
  m(){
    return x++;
  }
}
(new A).m(); // 0
(new A).m(); // 1

// vs

class A {
  var x;
  constructor(){ this->x = 0; }
  m(){
    return this->x++;
  }
}
(new A).m(); // 0
(new A).m(); // 0

The bit that confuses me here is that Allen himself has repeatedly proposed allowing variable declarations in class bodies as means of create normal variables in the scope of the class body, that is, variables which would be closed over by methods in the class body by the usual means and which have a single identity shared among all such methods and invocations thereof, including on different instances. That is after all how variables work in JavaScript.

We already use the term "instance variable" several years in many languages and loosely in JavaScript. For example DC's very old article: https://crockford.com/javascript/private.html already use "instance variable".

Having tutored a few brand-new JS programmers in the last couple years who had encountered Crockford's article or teaching materials based on them, I can now say with high confidence that that particular use of the term "variable" is extremely confusing to new programmers, and in my experience pretty much always causes them to have the wrong mental model of what's happening not just in that code but in the rest of the language.

@bakkot
Copy link

bakkot commented Mar 13, 2018

@zenparsing / @littledan

I claim that the delete trick for exposing private access is not sufficient, in the sense that it is not usable enough on its own to leave it as-is. It creates a void into which some kind of syntax must find its way.

I disagree. Like I say, I'd be fine with just the current fields proposal, or that + private methods. I wouldn't necessarily object to a more general construct which also satisfied that case; I just don't actually think that pattern is bad enough or use case common enough that syntactical support for something else is in fact inevitable. I think that pattern is plenty usable, especially if we're concerned about the overall complexity of the language. I don't especially like it, but I also really don't like adding more syntax, so...

@zenparsing

Furthermore, there are some issues with those proposals.

Agreed that there are some issues and edge cases, though I think they're much less of a big deal than you seem to. But this thread was mainly about complexity, and I wanted to say that this proposal seems to me to be much more complex than those do. (Also, specifically, "why can't I have private static?" seems like fundamentally less of a complexity cost than, for example, "why does this->x only sometimes do a brand check?". Code you can't write is simpler than code which requires several moving parts in a mental model to reason about.)

Part of the problem is that the current approach to developing class features is so open-ended that it's hard to tell what classes will end up looking like, or what features will end up in them. Narrowing our scope helps give us confidence.

That's why I keep asking for which specific proposals you intend this one to rule out. If it doesn't rule out callable constructors, or mixins, or decorators, or shorthand syntax for private field access, then it seems to me you have exactly the same problem: you just have the class fields and methods proposals, except with more syntax and no public fields, and no more or less of an ability to say whether any other feature will end up in classes.

That is to say, I don't think narrowing our scope actually does us give any additional confidence unless we specifically say which things are now out of scope for any future proposal. Since discussion in other threads (e.g. #33) suggests that this proposal is not intended to "finish" classes, it really feels to me like this has not been done. Absent that, if anything I think narrowing our scope gives us confidence, since we are then not considering interactions with those things which are now not in scope.

@hax
Copy link
Contributor

hax commented Mar 13, 2018

@bakkot Ok, I see.

But I will argue, for the guys who know plain old JS

class A {
  var x;
  f(){
    return this->x;
  }
}

is just sugar of

function A () {
  var x;
  this.f = function () {
    return x;
  }
}

This is the closure private pattern from ES3 era.

I understand some newcomers can't get it in the beginning, but when they finally learn the connection between class and function, they will not expect var x in class body refer to same var, just like you can't expect var x in function body refer to same var.

Having tutored a few brand-new JS programmers in the last couple years who had encountered Crockford's article or teaching materials based on them, I can now say with high confidence that that particular use of the term "variable" is extremely confusing to new programmers, and in my experience pretty much always causes them to have the wrong mental model of what's happening not just in that code but in the rest of the language.

Yeah, I still remember when I first saw "instance variable" in a C++ book 25 years ago, I was a little confused. But unfortunately, there are many "variable" term issues in programming languages, eg. global/static modifier VS normal variable in many languages are much more confused IMO. So I don't think "instance variable" is a big deal. 😝

@hax
Copy link
Contributor

hax commented Mar 13, 2018

BTW, @zenparsing I'm curious could we just use x as the shortcut for this->x ?

class A {
  var x
  f1() {
    return x + 1
  }
  f2() {
    const x = 2
    return this->x + x // only need this->x when there is a shadow x
  }
}

@bakkot
Copy link

bakkot commented Mar 13, 2018

@hax

is just sugar of

But it is not just sugar of that. There's only one function object created by the class definition. And it's especially confused given static blocks:

class A {
  static {
    console.log('reached');
  }
  var x;
  f(){
    return this->x;
  }
}

The static block runs just once at class definition time, but a new var x is created every time the class is instantiated.

@hax
Copy link
Contributor

hax commented Mar 13, 2018

@bakkot
Yes it's obviously not just sugar. This is the biggest limitation of the old closure private. My point is this proposal provide a good mapping for the old way. I don't think static is a big problem, we do not have "static" in all old pattern at all. And when ES6 introduce static method, all programmers already have to know the semantic difference between static and normal methods.

After all, var keyword usage is just a small problem IMO, I'm ok to switch to other keyword, like also use hidden or use instance. What you prefer?

@zenparsing
Copy link
Owner

@hax

I'm curious could we just use x as the shortcut for this->x

See #18 (and feel free to continue discussion in that thread, if you like).

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 13, 2018

@bakkot

which things we're considering to be class extensions proposals.

Max-min classes is an alternative to all of the following proposals:
Private instance methods and accessors
Class Public Instance Fields & Private Instance Fields
Static class fields and private static methods
Also the short hand syntax for private fields and method, which isn't yet on the official TC39 proposal page

It is orthogonal to and does not replace:
Decorators
but would require some modifications to that proposal. See #33

It does not address one way or another functionality address by:
Maximally Minimun Mixins

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 13, 2018

@bakkot

The bit that confuses me here is that Allen himself has repeatedly proposed allowing variable declarations in class bodies as means of create normal variables in the scope of the class body,

Yes I advocated for that as an alternatively way to remediate some of the issues with the private methods and static privates proposals. That experience is one of the things that convinced me a piecemeal or kick the ball down the field approach to extending class definitions was leading to too much overall complexity. It led me to agree to participate in developing this max-min proposal. In the context of the Max-min proposal I withdraw my support for class body lexical declaration .

Your variable binding argumenbts seem predicated upon the keyword var. Do they still hold if we replace var with any other word such as xyzzy? Or just #x;.

Instance variable declarations in our proposal do not create a lexical variable binding. They reserve an instance variable "slot" (if you will) in the per instance encapsulated state and introduce a lexically scoped (and namespaced) hidden name binding. The hidden name binding mechanism is essentially the same as used in the private fields proposal.

foo->x is not a variable reference to variable x, it is largely equivalent to foo.#foo in the private name proposal. Other then repurposing of the keyword var why doesn't your arguments apply to private fields?

@bakkot
Copy link

bakkot commented Mar 13, 2018

@allenwb, thanks for the list.

Your variable binding argumenbts seem predicated upon the keyword var. Do they still hold if we replace var with any other word such as xyzzy? Or just #x;.

No; I'm mostly just worried about var and about the language around it. Here this was specifically responding to a comment to the effect that "the old var is for function local variables, and this is for class 'local' variables", and to @zenparsing elsewhere emphasizing the importance of considering these to be "instance variables". I continue to feel they are not in any sense variables and should not be considered as such; I would be much happier if the syntax for declaration did not imply they are, as it currently does.

@bathos
Copy link

bathos commented Mar 14, 2018

@erights I find "slot" intuitive.

When the Chrome implementation of the private field proposal became available for experimentation, I tried it out by migrating a collection of custom elements that had been using WeakMap-based private state previously to see how it felt*. In my head "slot" was exactly how I was thinking about them — right down to the fact that, just like "slots" on DOM objects, methods referencing the 'slots' end up restricting what constitutes a compatible call context / receiver.

* Which was "very good", actually. I must be the only person who isn’t offended by # ... though I v. much appreciate the angle of this alternative proposal.

@hax
Copy link
Contributor

hax commented Mar 14, 2018

@bakkot

I claim this use of the term "variable" is bad, and we ought not continue the tradition (especially given that we have avoided it thus far, Crockford aside).

I don't agree term "variable" is bad, though I understand your concern.

The truth is, even you use other name, like "field", "slot"... you have no chance to change the traditional usage of "instance variable", many people/books will still use it to denote whatever spec name it, from the past to the future.

I think (and have a fair bit of personal experience to back up) that it confuses people, especially people new to JavaScript or programming in general, and can do long-lasting harm to their mental models.

I agree "instance variable" may confuse newcomers. But I don't agree it "can do long-lasting harm to their mental models".

I, as a non-english speaker, we suffer much much more pains from term issues, not only from the original books/docs/articles... but also from translations. Many terms have multiple translated names, and some different terms just use a same translated name! Eg. "attribute" and "property" are normally both translated to same word "属性" in Chinese. The term chaos is very common problem in Chinese programmers' life, but there is no evidence it did long-lasting harm to Chinese programmers' mental models. When we communicate to each other, if we say "属性", most time we will find whether it denote DOM property or HTML attribute from the context. Sometime we will say "DOM属性" or "HTML属性" if we anticipate the confusion.

I don't mean the preciseness of term is not important. If we Chinese programmers could have translated terms in more consistent forms , my life would be much much easier! But on the other side, we know the world is imperfect, and we can accommodate to it. The problem of "instance variable" vs "function variable" have no chance to worse than using same word to denote "property" and "attribute" IMO.

As JavaScript uses the terms, they are much more like properties than they are variables, to my eye. Properties are associated with objects; variables are not. There are other distinctions between the two, but that is the defining one.

To me, the difference of var VS prop is :

  • prop has descriptor, and prop access will follow prototype
  • function variable is much simple, it's just a name binding which hold a value/ref

I'm very glad class instance variable also very simple and very close to function variables in the old closure private patten, which do not involve prototype and descriptor.

@hax
Copy link
Contributor

hax commented Mar 14, 2018

I find "slot" intuitive.

It's very hard to predict which term is more intuitive. But as a non-English speaker, I will give you a interesting measurement of a term: whether there is a conventional translated name for a term.

In Chinese:

  • variable: we will always use 变量 to translate it
  • property: we will mostly use 属性 to translate it, but it's also very common to translate "attribute" to 属性
  • member: we will always use 成员 to translate it, but it must postfixed by 变量(variable) or 方法(method), or it will be confused.
  • field: we will mostly use 字段 to translate it, but not very common in JS. Most time we use 字段 to denote input field in HTML form or UI control
  • slot: we do NOT have conventional name for it!! Though some will use 槽 to translate it, but it's unlikely a Chinese programmer will know what you mean without concrete context.

If we finally use slot as the keyword, there will be a little pain for Chinese programmers in the beginning because we don't have a conventional name to translate it now.

I guess other non-English programmers could give similar observation.

@zenparsing
Copy link
Owner

As we debate the meaning of "variable" in the context of JavaScript, let's not forget that "global variables" are also a thing.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 14, 2018

@hax Your comments about translation are every enlightening. We native English speaker language designers typically don't even think about the difficulty of translating our hair-splitting terminology distinctions for other languages and cultures.

You should really writeup these observations about translation of programming concepts as a blog post or other standalone presentation.

@rhuanjl
Copy link

rhuanjl commented Mar 14, 2018

I may not be the most familiar with the technical details involved but from having read the existing proposals and compared to this proposal. The claim that this proposal reduces complexity when compared to the existing proposals seems simply untrue.

Allow me to elaborate:

1. Currently in javascript anywhere that you can declare a variable of any kind you can initialise it.
The existing proposal retains that. This proposal introduces a special type of variable that can NOT be initialised upon declaration - this is a new concept never before seen in javascript. -> ++complexity;

2. Currently in javascript var has a specific meaning
This proposal overloads var in a very different way. This is inherently going to confuse people. In general multi-use operators are complex/confusing for newcomers. ++complexity;

3. Static blocks - these have no comparison in existing JS or the existing proposals
Put simply these are a new feature with semantics different to any existing language feature. ++complexity;

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 14, 2018

@rhuanjl We are presenting our proposal as an alternative to the four proposals list in #32 (comment) . Our assertion is that our proposal is introduces less overall complexity then the totality of those four proposal. Any place we say less complex or reduced complexity we are implicit meaning compared to that other set of proposals.

But, of course it does add complexity to the language. Every addition to a language adds complexity along some dimension.

@rhuanjl
Copy link

rhuanjl commented Mar 14, 2018

I'm aware it is intended as a comparison to the existing proposals and I was implicitly comparing it to them in the above comments.

To make my points clearer:

Topic This proposal Existing proposals
Private/hidden variables/fields overload var for declaration # for declaration
"" must be declared and initialised separately declare and initialise in one go (like other JS variables)
"" this-> for access after declaration # for access after declaration
Private/hidden methods Declare with hidden Declare with #
"" Access with this-> Access with #
static blocks New concept with no existing correlate n/a

As far as I can see that is the comparison for the features this proposal includes VS the existing proposals. And in my view my points above on the increase of complexity stand for these comparisons.

The rest of the difference is that this proposal chooses to drop:

  • Public class fields (a neater/quicker definition syntax) which admittedly added no real functionality (other than a currently intangible optimisation potential)
  • Static public fields

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 14, 2018

@rhuanjl
You are only listing surface syntax difference. Most of the complexity in either approach is in the semantics that is behind that provides meaning to that syntax.

@zenparsing
Copy link
Owner

@rhuanjl

Whenever we talk about "complexity" we have to be careful about what beans we are counting. From a language designer point of view, we like to look at the complexity of interactions between different elements of the language. So while it may be "simpler" to just say "use # for private!", there are some interesting (and to us, worryingly complex) interactions going on under the surface.

That said, your worry about the reuse of var and lack of initializers has been noted by others as well.

@ljharb
Copy link

ljharb commented Mar 14, 2018

Is the primary reason complexity is a problem, though, because it makes language designers' life difficult? Or is it because it (eventually, perhaps) makes users' life difficult?

@littledan
Copy link
Collaborator

For a shorthand proposal, see https://github.com/littledan/proposal-private-shorthand .

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 14, 2018

@ljharb
Complexity makes everybody's life difficult. But in particularly, users'. Language designers and implementors eventually move on to other things, but users have to deal with the complexity everyday.

@arackaf
Copy link

arackaf commented Mar 14, 2018

Can you please help me to understand what sort of "complexity" these initializers cause users? The only thing I can sort of think of is that you have to know that for

class C {
  x = 12;
  foo = () => this.x;
}

foo will be parsed with this as the instance, but that seems to cause pretty minimal confusion in practice, from what I've seen.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 14, 2018

@arackaf
see #26 (comment)

Extrapolate to larger classes with more kinds of expressions in computed property positions and in initializers. In read the code, what scope will you assume for each expression?What order of evaluation will be used?

@arackaf
Copy link

arackaf commented Mar 14, 2018

I had assumed, and hoped for in-order evaluation, so

class C {
  @observable x = 12;
  @computed get xPlusOne(){ return this.x + 1 }
}

would work fine, and was relieved to hear that the spec did indeed demanded top-to-bottom evaluation.

As for scope, if you over-think it, then sure, the foo from my prior comment can seem weird, but again, it just fits well with how it's used, and hasn't in practice caused much confusion, as far as I can tell.

@zenparsing
Copy link
Owner

@ljharb

A wise person once told me that all successful software is doomed to turn into a ball of mud. JavaScript is an incredibly successful piece of software. We're just trying to put off the inevitable as long as we possibly can.

Complexity tends to manifest in special cases. As an example, under the current set of proposals, it appears that instance private "fields" and methods are OK, but static private is not (because of the prototype/TypeError issue). That is a special case. Does it make sense to the user? Probably not. And yet we still want to be able to securely decompose class-side methods, so now we need to invent some other feature to fill in the gap created by that special case. This is the process by which software becomes muddy.

@littledan
Copy link
Collaborator

littledan commented Mar 14, 2018

@zenparsing I don't understand how this proposal and the class fields proposal differ in this aspect. This proposal also omits "static variables".

@zenparsing
Copy link
Owner

zenparsing commented Mar 14, 2018

First, since this proposal does not introduce the concept of "fields", it does not tempt the user into thinking (by analogy with other languages) that there should be such a thing as "static private fields".

Second, it does provide support for class-side secure method decomposition via static hidden methods.

@bakkot
Copy link

bakkot commented Mar 15, 2018

@zenparsing, even if we did successfully manage to convince everyone to call these "instance variables" (which again I do not expect to be possible or desirable), those almost always have a counterpart of "class variables", i.e., static fields. So I don't really see why people would be any less tempted.

@hax
Copy link
Contributor

hax commented Mar 15, 2018

@bakkot I believe people will be less tempted because the usage of var keyword.

As I explained, var in class is a very good mapping of var in old closure private pattern. I believe if they understand (or be educated) this proposal this way, they will unlikely expect static var x in class because there is never static var x in function.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 15, 2018

@bakkot

would work fine, and was relieved to hear that the spec did indeed demanded top-to-bottom evaluation.

function () {
  class C {
    x = 12;
    [this.x](){ return this.x + 1 }
  }
}

What is the property name of the method defined above.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 15, 2018

@zenparsing

A wise person once told me that all successful software is doomed to turn into a ball of mud.

The Big Ball of Mud Pattern http://laputan.org/mud/

@hax
Copy link
Contributor

hax commented Mar 15, 2018

@allenwb

function () {
  class C {
    x = 12;
    [this.x](){ return this.x + 1 }
  }
}

To be fair, initializers do not contribute more confusion in this example. But the last example of x = this.name; is a shocked evidence of confusion.

As I understand, these examples demonstrate that if we want to introduce initializer, we should never allow lexical this in it. Which means:

class X {
  var foo = this.x // syntax error
  var foo = () => this.x; // also syntax error
  var foo = function () { return this.x; }; // ok, though not useful, 
                                            // because you can use hidden method instead
}

Does it solve the concern of complexity and confusion of initializers?

@rhuanjl
Copy link

rhuanjl commented Mar 15, 2018

To elaborate on my claim of this proposal introducing extra complexity:

  1. A new language feature with no correlate in the existing spec is an increase in complexity - it's something extra for a developer to learn the mechanics for.

  2. Overloading operators in similar but different ways = easy confusion traps for a developer/cause for mistakes.

  3. Different ways of referencing something depending on the context (i.e. declaration vs initialisation/use) = increased complexity. Currently JS has no correlate to the idea of "var" for declaration but this-> for initialisation/use. Yes I'm talking about top level syntax BUT I'm talking about top level syntax that is being proposed here being fundamentally different to anything currently in the JS spec.

If the problem is that computed property names in class fields would cause confusion this does not feel like the answer - "make the syntax confusing for most people to avoid edge cases that will cause weird errors for a few" instead IMO if those edge cases are such a problem prohibit them specifically rather than making the whole syntax messier.

Banning lexical this from any kind of class field (or equivalent)'s name seems eminently sensible to me and a far simpler fix.

@wycats
Copy link

wycats commented Mar 16, 2018

It is an experiment to see where we might end up if we start with a clean slate and took a minimalist approach to adding some pretty essential functionality that is not in ES6 class definitions.

I think it would be helpful if we separated out the syntactic changes in this proposal from significant divergences in semantics.

For example, this proposal shares the WeakMap perspective on private state (called "private fields" in the status quo proposal and "instance variables" in this repository) and other important semantic details of the status quo proposal.

On the other hand, it changes the surface syntax of accessing private state from object.#name to object->name. This syntactic change preserves the "inverted" semantics of the status quo proposal, as well as the semantics of name being a lexical bound private name that behaves similarly (nearly identically) to a WeakMap.

The authors of the proposal in this repository have concerns about the the surface syntax of the status quo proposal, as well as what they predict will be the likely mental model arising from that syntax. I think it would be helpful to separate out the syntactic changes from more significant semantic divergences (e.g. no initializers).

@hax
Copy link
Contributor

hax commented Jul 6, 2018

Maybe not the right place to ask.

@zenparsing @allenwb
Is there any new plan about classes 1.1 proposal? For example, create a babel plugin for it which can help the community to try it and compare it to current proposals?

@zenparsing
Copy link
Owner

@hax Thanks for following up! Unfortunately, the committee (for the most part) hated it. See tc39/proposal-class-fields#107 for a more recent discussion of a similar alternative. That alternative also failed to get traction.

I now wonder whether we are better off without any class-based private field syntax, at least for the time being. What do you think?

@hax
Copy link
Contributor

hax commented Jul 6, 2018

@zenparsing I know this proposal is still pre-stage 0, but I assume the stagnate of the process is because of the “sunk cost” from current already stage-3 proposals.

I happened to know one who attend the TC39 meeting in May, and he told me that he don't like current field proposals, (and even refuse to take the job of implement it in V8, as an active contributor of V8) but it's very hard to publicly show the support to this proposal, because it may be interpreted as the low evaluation of the work of his friends in tc39 who are pushing current proposals. I understand such situation of interpersonal relationships, it may be the problem of all the organization. (Someone in this thread may know whom I mentioned, please keep it secret because this is coming from a private conversation.)

So I think we need more feedback from out of the TC39. This is why I think babel may help. As babel 7 will ship #priv, I think it's better to also provide classes-1.1 as an alternative which all js programmers can try and compare, and give feedbacks.

(And I already make the most classes-1.1 syntax parsable in my fork of babel. See hax/babel@a274086)

I now wonder whether we are better off without any class-based private field syntax, at least for the time being. What do you think?

As I can say, private state per-instance is a rigid demand when using ES2015+ class. Private out of class is not a must have because we already have other facilities (local var, closure, module, symbol, etc.) This is why I am not very interested in https://github.com/tc39/proposal-static-class-features/blob/master/FOLLOWONS.md#private-names-declared-outside-of-classes or similar proposals.

And, in some comments I'v pointed out the problem of public field in OO theory/practice, the solution is private state wrapped by accessor. Without per-instance private state, we are very likely slip to flawed public field solution which current proposal advocated and TS already shipped. That's why I hope we can continue the work of classes 1.1, which is the most hopeful alternative to current proposals as far as my knowledge.


Note, if you are talking about just introducing private symbol (symbols which can not be reflected by Object.getOwnPropertySymbols()), without introducing any new syntax (or leave it for future proposals), I think it's a acceptable solution, personally. And I used to imagine this as the straightforward private solution until TC39 rejected it for some reasons related to proxy which I'm not fully understand. (though it seems tc39/proposal-class-fields#106 raise the problem again?)

But even private symbol was revived and I myself can accept it as the solution of private, I'm afraid the community may not be satisfied. We should land private symbol in ES2016, not ES202X!! The only chance of broad acceptance is provide syntax sugar for common usage like private state in class, then we go back to the mud of syntax 👻 .


PS. @ljharb I notice you 👎 the comment of @zenparsing , would you give more details about your thought? Though we may have many disagreements in the discussion, your idea is always important to me. Thank you.

PPS. To @zenparsing and all in TC39 (hope you can read it), Sometime I am disappointed about current status of TC39 { /*

*/ } but, it's the duty of TC39 to lead the evolving of the language. Even I believe myself may be as qualified as the member of TC39 if only consider the comprehension and experiences of the language and the community, the guys out of the committee like me eventually can only give the feedbacks and suggestions. You the members need to take the hard part. (Admitting we failed on a stage 3 proposal is definitely one of the hardest. ) And please be patient (to all the possible complains, misunderstandings, rants...). And don't give up. Thank you.

@littledan
Copy link
Collaborator

@hax I'd really like to make sure all of these perspectives are taken into account, both yours and other people with feedback. I appreciate all the time you've been taking to work to guide TC39 on these features. If you can convince them to be open with their concerns, I'd really like to talk with anyone who has problems with the current class proposals.

I think we need to think about how to make the committee more open to community feedback--GitHub often isn't the best medium for in-depth discussion, but at the same time it's definitely impractical to bring everyone into TC39 meetings (who would pay for all the free food for that many people?!?! 😄); I think we need some other way of managing discussions online and bringing people in. I totally agree with you that there are many people in the community who are just as qualified to help as the people who go to physical meetings. I'm pretty new to community organizing and outreach, so I'd be happy to hear any suggestions people have.

I've been trying to address all the concerns that I can. Some concerns are inherently un-addressable and we have to make a decision--for example, some people continue to say that classes as well as encapsulation are bad ideas. I'm glad to hear that you see encapsulation in classes with ergonomic syntax as an important feature as I do. At some point, we just have to decide as a committee and community whether we want these things or not. Some concerns are things that we can work through, either through tweaks in the proposals (e.g., the ASI discussion) or adjusting the scope (e.g., the discussion about static class features).

I'm trying to maintain an overall consistent, opinionated design philosophy with class features proposals, with a few core principles. The slogan I have in mind is something like, "# is the new _, except that it's actually not visible at all outside the class, which means some operations throw or are disallowed; basically, it's like using a WeakMap." I agree that we should keep vigilant that the language maintains good design and doesn't disintegrate into a jumble, and I've been trying to preserve that with these proposals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests