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

Why no initializers? #26

Open
ljharb opened this issue Mar 12, 2018 · 58 comments
Open

Why no initializers? #26

ljharb opened this issue Mar 12, 2018 · 58 comments

Comments

@ljharb
Copy link

ljharb commented Mar 12, 2018

One of the very valuable aspects of the current class fields proposal is that it makes it very ergonomic and easy to avoid having to write a constructor at all.

Being forced to write the constructor (the current state of things) allows the common footguns of forgetting to call super, of forgetting to call it with the proper arguments, etc - the default constructor's behavior of constructor(...args) { super(...args); }, in practice, is not intuitive enough for most people to properly replicate when they have to add a constructor (for the purpose of adding instance fields).

@allenwb
Copy link
Collaborator

allenwb commented Mar 12, 2018

Initializers add non-essential complexity. The natural scoping of such initializers is different from the scoping currently used in class bodies for computed property name expressions. Supporting that means in a sequence like:

class C {
   ...
   [this.name]() {...}
   var v = this.name();
   ...
}

The expression this.name will have two different meanings. This difference also suggests that different initialization orders have to be used for computed property names and for initializers.

All of that complexity is just not necessary. JS programmers already know how to initialize instance state. They do it by performing assignments within the body of a constructor. This well entrenched pattern works just as well for instance variables as it does for instance own properties.

Similarly, JS developers well understand the role of constructors. We don't think that the complexity that would have to be introduced to make constructors unnecessary in a limited set of use cases (note, there is no parameterization of initializers) is justified.

@bakkot
Copy link

bakkot commented Mar 12, 2018

JS programmers already know how to initialize instance state. They do it by performing assignments within the body of a constructor.

I think that a huge fraction of people who would think of themselves as JS programmers believe that the way you initialize instance state is class A { x = 0; }.

@allenwb
Copy link
Collaborator

allenwb commented Mar 12, 2018

I think that a huge fraction of people who would think of themselves as JS programmers believe that the way you initialize instance state is class A { x = 0; }.

In which standard edition of ECMAScript can you do that?

Note that we have explicitly stayed away from that syntax so that dialects such as TS and other transpilers based extensions can continue to support such syntax.

@bakkot
Copy link

bakkot commented Mar 12, 2018

In which standard edition of ECMAScript can you do that?

Notice my careful phrasing of "people who would think of themselves as JS programmers".

@rhuanjl
Copy link

rhuanjl commented Mar 12, 2018

Initialisers often help code be more self-documenting.
Yes you can have descriptive names and comments - but a value next to them adds to that.

Additionally banning initialisers from the declaration of instance variables would mean that in order to initialise everything all such variables would be listed twice.

In some large/complex classes with a lot of instance variables this could make the code unnecessarily long/less clear/more effort to update.

@justsml
Copy link

justsml commented Mar 13, 2018

On this issue - I bet 95% of devs using plain initializers (class C { x = 10; }) would be happy to leave out computed name expressions (class F { [this.dont] = 'nooo'}).

@allenwb

As for TypeScript concerns - sounds like we have the next MooTools 👊 😿

@RyanCavanaugh
Copy link

If you have initializers then you have to decide something that I think has no correct answer.

If you evaluate the initialization expression once during class setup and copy that value to each instance, that may be "too early", increases static startup cost, and creates enormous traps when the initializer is mutable (e.g. var arr = []). ~50% of devs will tell you this is the wrong behavior.

If you evaluate the initialization expression once per class instance, that may be "too often", and needlessly incurs cost when the initializer is a complex evaluation of an unchanging value. ~50% of devs will tell you this is the wrong behavior. It's also a trap because initializers observing virtual behavior [1] will presumably be running before any derived class constructors finish; people get this wrong all the time. See https://stackoverflow.com/questions/43595943/why-are-derived-class-property-values-not-seen-in-the-base-class-constructor / microsoft/TypeScript#1617

If initializers exist then the syntax needs to clearly imply which of these happen; I don't think it does (and thus think initializers should not exist)

@bakkot
Copy link

bakkot commented Mar 14, 2018

@RyanCavanaugh,

evaluate the initialization expression once during class setup and copy that value to each instance [...] ~50% of devs will tell you this is the wrong behavior

I would be very surprised to learn it's anything like that large a fraction.

It's also a trap because initializers observing virtual behavior [1] will presumably be running before any derived class constructors finish

Not having initializers does not save you. The following, as spec'd in this proposal, throws:

class Base {
  constructor() {
    this.init();
  }
  init(){}
}
class Derived extends Base {
  var m;
  init(){
    this->m = 0;
  }
}
new Derived;

because at the time that Derived.prototype.init is called, this does not yet have m.

Nor can this be trivially resolved by adding fields to this before invoking super, since the super call can return arbitrary objects.

@allenwb
Copy link
Collaborator

allenwb commented Mar 14, 2018

The following, as spec'd in this proposal, throws

As it should. Both the superclass coder is at fault for using an over-ridable method for instance initialization and the subclass coder is at fault for putting post construction dependencies into a over-ridden method that (hopefully) has been documented as being called during superclass construction. If you write subclasses you need to know the subclass extension contract of your superclass. But easily done correctly:

class Base {
  constructor() {
    this->init();
  }
  hidden init(){}
}
class Derived extends Base {
  var m;
  constructor() {
     super();
     this->init();
  }
  hidden init(){
    this->m = 0;
  }
}
new Derived;

@arackaf
Copy link

arackaf commented Mar 14, 2018

@allenwb

In which standard edition of ECMAScript can you do that?

This has been Stage 3 for ages, and has been the de facto way to initialize class state in at least one major JS framework (React). Not only is it shocking to see TC39 members argue for abandoning these features which are widely used, but removing the primary feature of class fields: initializers.

I must be missing something obvious, but what is the difference between

class C {
  x = 12;
}

and

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

(besides the added boilerplate)? The entire point of these class field, for a pretty sizable number of developers, was to avoid constructor boilerplate.

As others have said, this proposal seems to take a big step backward in usefulness, while creating significant churn in the JS community in the process. I struggle to see the point in any of it.

@Jessidhia
Copy link

@arackaf Also, the second one might invoke setters, and you have to be careful to correctly forward your arguments to super.

@arackaf
Copy link

arackaf commented Mar 14, 2018

@Kovensky - ah! Thank you - yes, I now remember a change to class fields whereby the

x = 12

would, iirc, now shadow an inherited field, rather than invoke the setter. Thank you!

@allenwb
Copy link
Collaborator

allenwb commented Mar 14, 2018

I must be missing something obvious, but what is the difference between

Complexity. Which you guys are demonstrating my trying to figure out the interaction between the initializer and inherited fields.

Understanding linear execution in a constructor method is easy.

@Jessidhia
Copy link

I see the declarative field as simpler from a mental model standpoint, as a declared field is just a field in your instance, and it avoids for you all the things you'd have to be careful with if you had to write it in the constructor instead.

Not only do you get to give it an initial value, you also get to avoid potential superclass prototype/instance setters, and you don't have to write the rest/spread arguments boilerplate to forward the correct constructor arguments to super.

@arackaf
Copy link

arackaf commented Mar 14, 2018

Which you guys are demonstrating my trying to figure out the interaction between the initializer and inherited fields.

I already understood that, I had just forgotten since it comes up literally never. And for those rare cases of deep inheritance chains with inherited properties, the class field initializer shadows the inherited property as most developers would expect (which is why, I assume, that change was made).

The initializers on class fields solved a significant problem many developers face. I really hope TC39 thinks better than to remove it under the (frankly patronizing) assumption that the status quo is too "complex."

@allenwb
Copy link
Collaborator

allenwb commented Mar 14, 2018

@Kovensky

a declared field is just a field in your instance

What is a "field"? It isn't a concept I remember reading about in any JavaScript book. Since you didn't say public field or private field I assume you are generalize over both. What are the semantics that both have in common that are relevant here?

@allenwb
Copy link
Collaborator

allenwb commented Mar 14, 2018

@arackaf

I already understood that, I had just forgotten since it comes up literally never.

Again, a sign of lurking complexity. Also not this isn't just about what JS programmer can understand and remember. Behind of this there is implementation complexity and language design complexity.

the class field initializer shadows the inherited property as most developers would expect (which is why, I assume, that change was made).

I assume you are referring to the fact that initializers for public fields (ie, own properties) set the property value using [[DefineOwnProperty]] rather than [[Set]]. But what happens it the initializer expression itself refers to an inherited get accessor. Is it visible or not? Do you know?

This is all a form of complexity. When you make common cases look simple by masking underlying mechanism you don't provide any foundation for reasoning about what is going on when you encounter a uncommon case.

@ljharb
Copy link
Author

ljharb commented Mar 14, 2018

I think what’s missing here is that there are use cases for which almost any complexity might be warranted. Features for the language are not evaluated solely on complexity; that’s just one of many considerations that come into play.

This proposal heavily prioritizes avoiding complexity while heavily deprioritizing desired use cases, years of committee consensus and work, years of existing babel codebases proving out the feasibility and desirability of the current proposals (and demonstrating that a very large number of JS programmers seem to intuitively understand how initializers work without explanation), and while overlooking very large consistency and feature holes in the ES6 design that the current proposals fill but this proposal fails to fully satisfy.

@arackaf
Copy link

arackaf commented Mar 14, 2018

Behind of this there is implementation complexity

Did implementation concerns not get ironed out prior to this getting to Stage 3?

and language design complexity

All language features have hidden "complexity" (what I would call trivia") lurking behind the scenes. I was just reading Axel's Exploring ES2018 and ES2019. I wonder how many JS devs don't know the difference between object spread, and Object.assign, as it pertains to property setters vs defining new properties, and yet manage to use this feature productively nonetheless.

@ljharb
Copy link
Author

ljharb commented Mar 14, 2018

@arackaf ftr, stage 3 is when implementation concerns are intended to get ironed out, not stage 2.

@arackaf
Copy link

arackaf commented Mar 14, 2018

@ljharb thank you for clarifying. I have heard of the Chrome team objecting to proposals prior to reaching Stage 3, but I'll take you word on that in the general case.

@ljharb
Copy link
Author

ljharb commented Mar 14, 2018

Being a browser doesn’t exclude objecting at earlier stages :-) certainly any known implementation issues would be brought up as early as possible; it’s just expected that stage 3 is when engines actually implement it, and thus are able to bring concrete feedback to the champion/committee.

@zenparsing
Copy link
Owner

@ljharb @arackaf

I understand that this proposal seems very disruptive from the point of view of people who are used to thinking of public fields (with initializers) as "the way it's done", and I sympathize.

Of course, it must be noted that lots of React developers think of other non-standard JS extensions as the "way it's done" as well.

A clarifying question: are you objecting to the lack of initializers on instance variable declarations (as the thread title indicates) or more generally to the lack of public fields?

@arackaf
Copy link

arackaf commented Mar 14, 2018

Of course, it must be noted that lots of React developers think of other non-standard JS extensions as the "way it's done" as well

We're well aware that JSX is non-standard, and that we'll always need tooling to support it. The concern is that these class features have been dragging along for literally years, and now, just as instance fields are starting to get implemented, and decorators were about to hit stage 3, you all want to roll this back to the drawing board in order to fix problems which don't in reality exist, while making these features less useful in the process.

And yes, I understood this thread to be about initializers on instance fields, ie

class C {
  x = 0;
  inc = () => this.x++;
}

The use of initializers is, I think it's accurate to say, the most common reason devs reach for class instance fields.

@ljharb
Copy link
Author

ljharb commented Mar 14, 2018

@zenparsing I’m specifically objecting here to the lack of initializers; but related, to the inability to omit the constructor, which is a very critical feature of the current proposals. I don’t care if they’re called “fields” or not, i care that i can omit the constructor and define a per-instance initializer expression. If this proposal can offer that, then I’d feel it’s at least be a viable alternative - as of now, it is not one imo.

@hax
Copy link
Contributor

hax commented Mar 14, 2018

While I understand @allenwb 's concern of complexity, I also understand initialization is a very wanted feature, especially for those (like, React users) who do not use deep inheritance at all.

Is it possible we can advance this proposal, but keep the door open for future support initialization? I think a controversial but important feature like initialization deserves a separate proposal.

@allenwb
Copy link
Collaborator

allenwb commented Mar 14, 2018

@hax

Is it possible we can advance this proposal, but keep the door open for future support initialization? I think a controversial but important feature like initialization deserves a separate proposal.

The door is always open.

Also, there is nothing preventing a trasnspiler based support for adding initializers to instance variable definitions including generating the necessary constructor if one is not present. Having the features in those proposal built-in to engines provides a more solid and consistant base for such transpiler-based features then what we have today.

@arackaf
Copy link

arackaf commented Mar 14, 2018

Also, there is nothing preventing a trasnspiler based support for adding initializers to instance variable definitions including generating the necessary constructor if one is not present.

That's what the TypeScript compiler, and Babel transforms do today. The value in having these features supported natively is that we don't (wouldn't) need those tools anymore. A language proposal which needs tooling support in order to satisfy essential use cases seems inherently flawed.

@hax
Copy link
Contributor

hax commented Mar 14, 2018

@arackaf You always need TS for static type system, and you always need Babel for JSX support.

satisfy essential use cases seems inherently flawed

We should agree JSX/static type system is very very important for React/TS users, which definitely have "essential use cases"! So if JS do not support JSX/static type system, it seems JS will always "inherently flawed"!?

On the other side, you ignore the possible complexity of initialization because you may never use deep inheritance and will unlikely meet the edge cases.

I don't think it's a fair argument which treat initialization usage as "essential" because of React heavy usage, and on the other side treat confusion risk as "not essential" because React discourage of inheritance.

With the same logic, other users who rely on deep inheritance will treat confusion risk as "essential" and initialization convenience as "non-essential".

NOTE, I'm not against the idea of initialization. Some teams in my company also use React heavily and myself is a TypeScript lover and I always use initialization AMAP.

I just think we don't need to land all features in one time. As my understanding, we still can add initializer in the future. So this should not be the block of this proposal. The main advantage of this proposal IMO is it solves the big issue of #priv syntax, which can save the whole community from disorder.

@hax
Copy link
Contributor

hax commented Mar 14, 2018

It seems no one discuss @allenwb example, so I will try:

class C {
   ...
   [this.name]() {...}
   var v = this.name();
   ...
}

I have to admit, this example shocked me! I always believe I already know 99% of dirty part of JS, but this example just humiliate me.

A good excuse is we never write code like that. But I just realize [this.name]() {} is already in ES6 years and it has a fair reason to support it.

On the other hand, I believe @justsml 's comment "95% of devs using plain initializers" is reasonable. So I doubt if it possible to limit the power of the initializers:

Disallow this/super which could eliminate the confusion in this example, and also eliminate the possible access of inherited method/accessor/property.

class C {
   ...
   [this.name]() {...}
   var v = this.name() // syntax error
   var x = 1 // ok
   var y = x // ok if we support shortcut for var
   var z = f() // syntax error, we can't support shortcut for hidden method
   hidden f() { return this.prop }
   ...
}

[update] I just realize that my proposal here is exactly the C# way for field initialization! And in C# way, fields initializer will be executed before constructor (in the order: 1. subclass fields init, 2. superclass fields init, 3. superclass constructor, 4. subclass constructor), this can make @bakkot example valid.

What do you think?

@arackaf
Copy link

arackaf commented Mar 14, 2018

@allenwb One point about initializers which I don't think has been brought up here, is that they greatly help type systems like TypeScript and Flow. They, as of now, seem to have difficulty typing things like

class C {
  constructor(){
    this.x = 0;
  }
}

requiring you to add boilerplate like this

class C {
  x: number;
  constructor(){
    this.x = 0;
  }
}

while initializers give you the best of all worlds:

class C {
  x = 0;
}

Again, respectfully, I think removing this feature at this point would cause immense harm to the community.

@arackaf
Copy link

arackaf commented Mar 15, 2018

Can we back up for a minute? If initializers are not essential since you can just use constructor initialization, why isn't private state similarly inessential since you can just use a WeakMap?

@hax
Copy link
Contributor

hax commented Mar 15, 2018

@arackaf

why isn't private state similarly inessential since you can just use a WeakMap

To be honest, I'm fine with current feature set of class. And I even don't need WeakMap because soft private by Symbol is ok for most cases. And TS compile-time only private is also a solution. But the requirements of a real private has a very long history start from ES4 era. It seems it's already be postponed many years and times. Whatever programmers like or not, class field proposal which use controversial this.#priv syntax, is already arrive stage 3. As I understand, this proposal give another solution of private is a try of save us from the expected storm of boycott of # and the break of the community, when the former finally arrived stage 4.

@arackaf
Copy link

arackaf commented Mar 15, 2018

Yeah I'm fine with backing up and re-trying private state. It's the ditching of widely-used instance field initializers (and requiring yet more changes to decorators) that's disconcerting.

@littledan
Copy link
Collaborator

As I understand, this proposal give another solution of private is a try of save us from the expected storm of boycott of # and the break of the community, when the former finally arrived stage 4.

I don't understand; do you think the negative reaction here will be worse than that of other JavaScript features, such as classes themselves? I don't really see why the community would break (any more than it already is) due to the # proposal.

@arackaf
Copy link

arackaf commented Mar 15, 2018

I don't really see why the community would break (any more than it already is) due to the # proposal.

I agree. The JS community is SO unbelievably high-maintenance. The whining and complaining about every little thing is far beyond anything I experienced when I was a C# developer.

But yeah, at the end of the day, everyone winds up shutting up, and shipping. I don't think it would be any different with #. People would get over it pretty quickly. And frankly I think # was better than this current mess of hidden var and ->, but that's a separate discussion from this issue.

@bathos
Copy link

bathos commented Mar 15, 2018

@arackaf

If initializers are not essential since you can just use constructor initialization, why isn't private state similarly inessential since you can just use a WeakMap?

The differences I see between the two cases have to do with expressivity and "model rationality". I don’t know if this matches up with any of the motives here quite and it may be inaccurate, but here’s how I would maybe answer that (partly devil’s advocate-y):

Private instance state achieved via WeakMap is inexpressive and entails a lot of boilerplate, especially if you want to provide sensible error messages when functions are called with incompatible receivers. Further, relying on WeakMap rather than syntax means that privacy depends on an untampered-with WeakMap intrinsic. Only syntax can get around that.

The "public fields" syntax in contrast may increase clarity and readability for many common cases, but it doesn’t increase expressivity or achieve anything currently unaccounted for. It might even be argued that it reduces expressivity, since the shorthand obscures the fact that these new class members amount to a series of to-be-evaluated-later imperative statements. The existing syntax for "to-be-evaluated-later imperative statement list" is "function body", which seems reasonable here and is provided for explicitly by constructor.

Relatedly, it’s surprising if a feature whose purpose is "just ergonomics" adds complexity to the model. These substantially order-dependent imperative members describing initialization logic are interspersed with declarative members that describe the prototype and constructor, and together they compose a sort of "second constructor" which must be run not before or after the first constructor but rather after the super call or would-be super call of the "first constructor". This seems like a disproportionately complex need for supporting syntax sugar and I think maybe provides a hint that it’s kinda "off". Why are these property assignments different from other initialization logic, and what is the rationale behind their needing to occur prior to other initialization logic? Could most devs using this syntax say "when" those expressions will get evaluated?

To disclose: I’m not personally opposed to new syntax for initializing public properties in some form — I’ve had reservations (esp wrt eating up the logical syntactic space for "non-method value property of prototype"), but I’ve come around to it, and I use it myself now. But I still wonder about the things I tried to describe above, and anecdotally have found that many people don’t seem to really "get" what property initializers are doing, and tend to use them in places where prototype methods would be better because it ends up becoming a guessing game. That has made me worry about it a bit again — this confusion was less common before using the new syntax.

@zenparsing
Copy link
Owner

I'm not sure that "they'll get used to it" is a very good approach for JavaScript.

A specific example of the worry that @bathos is describing: the currently popular practice of using fields initialized to arrow functions to "fake" methods, resulting in inheritance problems.

@arackaf
Copy link

arackaf commented Mar 15, 2018

One of the essential features these class instance fields allow is use with decorators.

Previously we've used things like

class Thing {
  @required
  @observable
  x = 12;
}

That's one of many things people will need to painfully remove or adjust since, apparently, this model has been too complex all along.

@arackaf
Copy link

arackaf commented Mar 15, 2018

using fields initialized to arrow functions to "fake" methods, resulting in inheritance problems

citation needed. This is not a problem in practice, in my experience.

@zenparsing
Copy link
Owner

There's this.

It may not be a problem in the context of React, since you rarely extend a subclass of Component. But I think the fact that these are generally called "arrow function methods" is a bit concerning. It would probably be better to have some kind of language support for efficient method extraction, rather than relying on instance properties that look like methods.

@arackaf
Copy link

arackaf commented Mar 15, 2018

That's a horribly, horribly contrived article. Have you ever, honestly, in your history of coding in JS, written a unit test verifying that a method exists on className.prototype?

And yeah the perf is worse - it's per instance. Which is the whole point.

@zenparsing
Copy link
Owner

I feel like you set me up for that. 😄

Anyway, the point about inheritance is valid. I'm not trying to say that React users are "doing it wrong". I'm trying to say that there might be a better way.

@arackaf
Copy link

arackaf commented Mar 15, 2018

It's not just React users. See my comment third up from this one. That's a popular idiom in MobX, which certainly is NOT tied to React (though it's popular there, of course). And I believe Glimmer works similarly.

Do whatever you want with private, but please do not kill this popular feature! :)

@bathos
Copy link

bathos commented Mar 15, 2018

@arackaf is it incorrect to say that the absence of instance property initializers in this proposal would not preclude them being added by another proposal? Although their absence here feels a bit like a "stance," my impression was that nothing here prevents them.

@arackaf
Copy link

arackaf commented Mar 15, 2018

@bathos well sure, obviously if this proposal were to kill them off, another proposal could come along and add them back. The point is, there should be a pretty ironclad, outrageously good reason to kill off such a widely used feature this late in the game.

@rhuanjl
Copy link

rhuanjl commented Mar 17, 2018

Whilst the absence of initialisers in this proposal does indeed not ban a future proposal from adding them - it is very clearly part of the intent of this proposal's authors to block such from being added.

They consider them to be too complex. (Obviously they're also dropping public class fields as without initialisers there's no point in them)

@hax
Copy link
Contributor

hax commented Mar 19, 2018

@littledan

do you think the negative reaction here will be worse than that of other JavaScript features, such as classes themselves?

Yes, the negative reaction of #priv is worse, I think it's the worst in our history. I'll try to explain it.

Same time last year, I already convinced myself we can live with#priv, so I tried to introduce it to others.

In my experience, when programmers see a new feature, it's common that someone like it, someone dislike it, and someone is uncertain. As a good speaker with a solid comprehension of the whole language, most time I can convince 90% of those who is uncertain or dislike it that the feature is useful and reasonable.

For example, class. I know a lot of js programmers think class is unwanted. But you can talk to them to see how they really write code. Suppose, they told you they prefer OLOO instead of class. Then you could just ask them if you always write obj = Object.create(parent); obj.init(...args); then what's wrong to wrap it to obj = createMyObj(...args) as DRY principle, or even use obj = new MyObj(...args) which is a standard way to create a new instance?

Note, I'm not here to prove OLOO is bad, just use it as a example. I believe the guys invent and advocate OLOO has their good reasons, but most person never really think deeply of all such so called patterns/good practices, they just learned it from some place and swallowed it. So you can just show them the other side, other choices which they never really considered before.

But I found #priv case is very different, I never get a one positive feedback when I introduce it to others, from average to senior. The first thought are very similar: are you kidding me? WTF! Ugly! This is wrong. We can do it better...

Even I finally convinced them this is what the best we can get (before I see this proposal), and your dislike is just aesthetic prejudice which make no sense in real coding, they still feel unhappy, and some think they will never adopt it. (they think symbol-based solution or TypeScript compile-time private is sufficient.)

So I realized what's the different of #priv case with others in the history. This is solely a syntax issue. Though this proposal is also have other divergences with current proposals, and I believe they are important in long-term, they are not very important for acceptance of the most js programmers. I know this is stange, and most language designers may not understand or agree, #priv is the most trouble issue in our history. It's even not a big syntax issue like ASI, it's a isolated issue, this is why it's the most trouble one.

Because, it's just a personal taste. It's nothing wrong that one is just unlike # in their code. I have many ways to show you why one pattern is worse than other, why one usage is inconsistent, error-prone, hard to maintain... I even convinced many adopted semicolon-less style in last ten years. But I can't prove that # is better than anything. The only I can say is like "we don't have other choices..."

So I failed on #priv case. We failed on it.

I don't really see why the community would break (any more than it already is) due to the # proposal.

It will break, the reason I have mentioned before -- some will never adopt it.

It's ok that someone keep using OLOO. The key point of class is, if you accept class-style, you now have standard, syntax/semantic consistent way to do it.

#priv is different. Though most of us believe we need private, but they refused to use a united syntax,and therefore no same semantics. Some use underscore prefix name convention, some use symbol, some use TypeScript private, some use WeakMap, some use old closure private pattern, some may invent others... and some use #priv.

Note, as a heavy TypeScript user, I understand TS will keep support their privates for a long time, it's important for TS community. But that doesn't mean keep using two is a good idea in long-term. You of coz can use both so-called design-time private and js native runtime-private. But to be honest, the difference of two is too small and not very important in daily programming. So it will be a meaningless burden for team and most js programmers to think about which one (or both) is right for each fields/methods of each class. Finally we have to move to JS native private.

To be clear, the masses of js privates is not the fault of #priv, but a problem it need to deal with. A good proposal should not just add a new alternative. So I believe even this new proposal may only increase 10% of adoption than #priv, it will a big win.

Thank you for listening.

@hax
Copy link
Contributor

hax commented Mar 19, 2018

It's not just React users. See my comment third up from this one. That's a popular idiom in MobX, which certainly is NOT tied to React (though it's popular there, of course). And I believe Glimmer works similarly.

On the contrary, this is really BAD. It's only ok if just React users use such pattern, because in the context of React, you have other practices and conventions which avoid the dangerous part of it.

When it spread to other place, you will lose the context, lose the protect, and finally it will bite you or others somewhere. This is why we call such thing is anti-pattern.

Do whatever you want with private, but please do not kill this popular feature! :)

Again, it's no way to kill a feature or pattern when you can always use it via Babel/TypeScript. We just don't convinced public field is a must have in native JS.


More about public field.

Note, in OO theory, public field is a questionable feature. Languages support them just because it seems very strange/inconsistent to disallow public field when you have all public methods, private fields and private methods. But the linters / compilers / your reviewers may complain about public fields if you really use it. They will tell you you'd better use public accessors wrap private field.

On the other hand, In JavaScript world, we never have native private, so even you use accessors, you still need a "public" property to store the value. This is why we are rarely use accessors pattern like other languages. This is another reason why I think Babel/TS experiences are not sufficient enough to prove the usefulness of pubic field, because without real private support, you have no other choice.

@Jessidhia
Copy link

Even in the context of React, the only place where you'd use a public field is when you have something that needs to be accessible by React itself (e.g. state), not because you actually want random other first party classes to be able to poke it.

@arackaf
Copy link

arackaf commented Mar 20, 2018

GitHub comments declaring class instance fields "bad" notwithstanding, they are widely used in the JS community, even outside of React. MobX uses them heavily, as does Glimmer. I would encourage any would-be thought leader to actually look at how this idiom is used in practice before declaring it "bad" on the basis of abstract OO theory.

@ljharb
Copy link
Author

ljharb commented Mar 20, 2018

off-topic followup question to @arackaf can you link me to those comments, off-thread if possible? I've only stated that *arrow functions* in class instance fields are "bad", but I think the fields themselves are awesome and hugely useful.

@MichaelTheriot
Copy link

MichaelTheriot commented Mar 20, 2018

That's a popular idiom in MobX, which certainly is NOT tied to React

I am not up to speed with the state of front-end web development frameworks in 2018, so forgive me if this comes off as ignorant, but MobX's repo is tagged with "react", says React and itself are a "powerful combination", and includes React as a dependency?

MobX uses them heavily, as does Glimmer

Sorry, I have never even heard of Glimmer.

Having never used this, which should be considered the norm, it is not apparent how initializers are essential. I would appreciate seeing more use cases rather than hearing a lot of people use it. This largely sounds like a "nice to have" if the only major use is not writing 2-3 lines.

@arackaf
Copy link

arackaf commented Mar 21, 2018

npm lists no dependencies of MobX; I'm not sure where you're getting that.

https://www.npmjs.com/package/mobx

Glimmer is the new state manager for Ember.

This is also used in TypeScript and Flow.

Familiarizing yourself with the state of front end web development will help you see where and how this feature is used.

@MichaelTheriot
Copy link

MichaelTheriot commented Mar 21, 2018

Misreading the package.json file, sorry (it's the second file to pop up when you search "react"). That doesn't dismiss the fact that this idiom is being used in a project that strives to complement React.

OK, but if this is deemed essential it should not be hard to provide a strong example broader than this context as well.

@hax
Copy link
Contributor

hax commented Mar 22, 2018

@arackaf

declaring it "bad" on the basis of abstract OO theory

It's not abstract OO theory. The programmers of other languages keep vigilance against public field for good, both theoretical and practical reasons. Read:

You could find more if you want.

Of coz, some problems (eg. binary compatibility of public field and property) do not exist in JavaScript. But we have another trouble problem. It's not real field like Java/C#, but a instance property. It may be not important to you, or all React users. But you should not assume no one use inheritance.

GitHub comments declaring class instance fields "bad" notwithstanding, they are widely used in the JS community, even outside of React.

Widely usage does not necessarily mean it's the best option. As I explained several times, we use public field in TS/Babel because we do not have other choice. But if hidden var landed, accessor pattern will be really available to JavaScript programmers, and we can see what happen. For example I expect @getter @prop decorators would emerge.


Can we focus on initializer issue in the thread? I hope someone will inspect the possibility of my proposal in previous comment: #26 (comment)

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

No branches or pull requests