proposal-class-fields icon indicating copy to clipboard operation
proposal-class-fields copied to clipboard

The argument against this proposal

Open rdking opened this issue 5 years ago • 23 comments

I think I've finally figured out how to word the 1 argument that everyone against this proposal has in common, that I haven't seen any evidence of being considered or argued in the past. I've been trying to make this argument over the past 3+ years with no success, likely because the wording was always directed toward some feature. Admittedly, TC39's stance on its decisions is unassailable for various reasons. So from that point of view, it's flatly impossible to get any of them to change their minds. However, what if I present the argument like this:


The core problem with this proposal, that precipitates all of the issues that are constantly being re-hashed to no avail, is that this proposal does not give adequate consideration to the most common use cases for the features being offered. This requires understanding not only the technical aspects of the design, but also the practical aspects of how it will likely be used by developers. While TC39 has done its best to provide us with a proposal that meets some here-to-for undocumented collection of requirements, it is at best questionable whether those requirements are in alignment with the practical needs of the ES developer community.


As it was explained to me at some earlier point in this proposal, the purposes of adding new syntax are to:

  • Simplify the use of commonly used "best practice" use cases that are currently not ergonomic
  • Encourage the use of "best practice" use cases
  • Provide for new features and use cases that are not currently part of the language in a non-interfering fashion

It's that 3rd position that raises an issue. TC39 is currently taking that statement to mean that no existing code base will be functionally disrupted by the existence of the new feature. From the standpoint of an ES developer, while helpful, this is neither a viable nor tenable stance. It may be better if TC39 could take that statement to mean that neither existing code bases nor existing programming paradigms will be needlessly disrupted by the existence of the new feature. Giving weight to how the majority of developers use the language is the important part.

Here's an example of the problems in `[[Set]]` vs `[[Define]]` from the standpoint of prioritizing how developers use the language.

For instance, in the [[Set]] vs [[Define]] debate, TC39's consensus was in favor of [[Define]], their reasoning being that this makes clear the effect of initializing a field in a class definition. This, however, is in immediate opposition to how the language is currently used. When an object with a non-trivial prototype chain is created, assigning an initial value to a property of the object is first checked against the prototype chain since there may be a getter or setter.

This is a non-trivial part of the prototypal design of the language. This is also something that every developer expects to be true. In all fairness, it is true that this only affects inheritance chains (especially ones containing accessors), but isn't that one of the major up-sides of using class in the first place? If there is no intention on using inheritance at some point, there is virtually no advantage in using class over a factory function.

The worst part about this decision is that on the one hand, the choice has been made to allow initializers to run at instantiation time instead of class definition evaluation time. This leads directly to the misconception that a constructor is not necessary if it would only be used to initialize field values. This leads to the case where either one is left with needing to remember which fields can be assigned in the definition and which must be assigned in the constructor, regardless of whether or not accessors are used.

This is truly a complication developers do not currently need to worry about. Trading in the need to have knowledge of the API you're extending for this added complication is definitely outside ES developer expectations, and a hard break in many common use cases involving inheritance. Encouraging the use of this feature is therefore essentially inviting hard to debug problems.


I can provide an equivalent common use case summary for each of the major issues contained in this proposal. All of them are issues where inadequate consideration (despite the many years of discussion) was given to how developers are commonly using the language. If there is any room left for discussion on this proposal at all that is not essentially re-arguing the same points again, it is likely to be in this topic of how most developers actually use the language.

rdking avatar Jul 08 '19 05:07 rdking

Encourage the use of "best practice" use cases

New best practices may now be:

  • If you're a framework/library author,
    • do not use class fields. They may override user subclass accessors.
    • do not use concise accessors. Users may unintentionally override them with class fields.
  • If you're writing an application,
    • look at the source code of your frameworks and libraries, not just the docs, to know if they use class fields or not.
    • don't use class fields, as they may break frameworks/libs that you are using.
    • don't make concise accessors, they may become useless if the frameworks/libs you are using change to class fields.

🤣

trusktr avatar Jul 08 '19 21:07 trusktr

problems in [[Set]] vs [[Define]]

I feel that it would be so simple to just make [[Set]] the default, with a syntax for [[Define]] like define foo = 123. Problems would go away, and it would increase awareness of the two different ways to define a property.

At the very least, ship this with a syntax like set foo = 123 so we have an escape hatch (and watch set become the new best practice).

trusktr avatar Jul 08 '19 21:07 trusktr

What if we made an eslint plugin for no-private-class-fields and used download counts to illustrate community opposition? It could have a postinstall splash screen that says "Tired of people trying to ruin your language? We've got just the repository for you to let them know!". 😈

brianjenkins94 avatar Jul 21 '19 17:07 brianjenkins94

@brianjenkins94 As funny as that sounds, it probably would still fall on deaf ears with excuses about how you don't really have a representative sample, or about how bad it is to design by public opinion.

rdking avatar Jul 22 '19 00:07 rdking

The core problem with this proposal, that precipitates all of the issues that are constantly being re-hashed to no avail, is that this proposal does not give adequate consideration to the most common use cases for the features being offered.

I think it is good to include in all feature proposals multiple use cases solved by the proposal, not just imply it for the reader.

Case in point, I do not see a major use case for public fields in this proposal's readme. It mentions that you can now declare 'fields' outside of the constructor, but does not illustrate what problem this solves. Being able to declare fields outside of the constructor is neat, I guess, but comes with its own baggage that clouds whatever merit it brings. It seems to go against the max-min philosophy of ES2015 classes, and it is confusing to me that it comes bundled with other features and is not a separate proposal entirely.

MichaelTheriot avatar Oct 19 '19 06:10 MichaelTheriot

@MichaelTheriot

It mentions that you can now declare 'fields' outside of the constructor, but does not illustrate what problem this solves.

That's because it doesn't solve a problem. It only partially and indirectly eliminates and inconvenience. You know the rule where if you use extends in conjunction with a constructor, you must also use super()? That's problematic for those who don't want to know the requirements for initializing the base class. Pass the wrong arguments and the program may crash.

That is very literally the only thing that's even close to a problem solved by public fields. Beyond that, it's just a nice convenience syntax (with improper semantics).

rdking avatar Oct 19 '19 17:10 rdking

There are many others, including static analysis (linting) for public fields, to make sure a class follows a convention. For example, react components declaring their state.

ljharb avatar Oct 19 '19 17:10 ljharb

Static analysis isn't a problem for the language as much as a problem for tooling. Sure with static analysis, an optimized compilation is quicker to achieve. That's definitely a good, but it shouldn't come at the cost of the flexibility and expressiveness of the language, which ES is known for and should take pride in.

As for trying "to make sure a class follows a convention", the convention being followed should exist only in 1 place, the developer coding it. If I were stuck with the conventions you personally follow when writing code, there's a lot of creative things I wouldn't be able to do that are quite useful. Likewise the other way around. The language should only make sure we're not trying to accomplish something technically foolish. In other words, it is best for a language to be as un-opinionated about the small details beyond its base paradigm as possible. This proposal does exactly the opposite, crippling usage patterns for the cost of its direct and/or indirect use.

rdking avatar Oct 19 '19 18:10 rdking

You know the rule where if you use extends in conjunction with a constructor, you must also use super()? That's problematic for those who don't want to know the requirements for initializing the base class. Pass the wrong arguments and the program may crash.

That is very literally the only thing that's even close to a problem solved by public fields. Beyond that, it's just a nice convenience syntax (with improper semantics).

This is a good example of what I expect in the readme. I had never considered this. It would be nice to see examples of this in the readme to validate this benefit.

MichaelTheriot avatar Oct 20 '19 17:10 MichaelTheriot

For example, react components declaring their state.

This is the same exact use case the Public Fields proposal mentioned in its first publication.

In fact, React is almost always brought up as the use case for public fields (and comes up in statics)...

  1. https://github.com/tc39/proposal-class-public-fields/tree/c3d2f4da26dde486018aaffcd4bf91615cedfa1f#why
  2. https://github.com/zenparsing/js-classes-1.1/issues/26#issuecomment-372883202
  3. https://esdiscuss.org/topic/class-method-shorthand-return
  4. Public Class Fields saves sooo many keystrokes in React code
  5. Class fields are coming: here's what that means for React
  6. https://esdiscuss.org/notes/2017-11-29 (your own comments)
  7. https://github.com/tc39/proposal-static-class-features/issues/27#issue-311803685 (the "other" use cases all being web frameworks, like React, all benefiting from public fields to apparently solve the same issue as React)

There's more, but I think my point is clear: there are ample examples showing how this feature significantly benefits a singular case, but little else.

IMO, many unique examples of the problems these features solve should be provided to justify adding changes to the language unable to be used in older environments.

i.e. a developer should not feel that this change to the language has been incorporated primarily to benefit a single use case for React / functionally similar libraries, which I suspect will be forgiven by the existence of Babel (well-known to React developers already).

MichaelTheriot avatar Oct 20 '19 17:10 MichaelTheriot

IMO, many unique examples of the problems these features solve should be provided to justify adding backward-incompatible changes to the language.

Be careful with the wording of your argument. Technically, class-fields isn't backwards-incompatible since the syntax has never existed in the language. That it can cause problems when used in conjunction with older code is definitely an issue, but that doesn't make it backwards incompatible.

rdking avatar Oct 20 '19 20:10 rdking

Thanks, corrected.

MichaelTheriot avatar Oct 20 '19 21:10 MichaelTheriot

I've just discovered this proposal and have been reading around it for a few hours, which I realise is not much time given the context of the timeline of this proposal. However, all the arguments for this feature seem to originate from library authors and application builders, which is fine, private fields have their uses in those contexts, namely server side or desktop applications. I haven't seen many discussions about the use on websites and the side effects of it.

Has there been any consideration how this is going to affect how "open" the web is? At the moment, websites are able to be modified and tinkered with by end users, either with custom scripts they write themselves or by browser extensions. This proposal provides a way for websites to make that process considerably more difficult to achieve (dare I say sometimes impossible) as it removes the ability to monkey patch code.

There are a huge amount of browser plugins which rely on this ability to modify behaviors of a websites.

marlon-tucker avatar Jul 03 '20 16:07 marlon-tucker

@marlon-tucker it will affect it zero, because "variables inside a closure" have had precisely this amount of privacy forever, and nobody's found themselves unable to tinker. Private class fields are effectively just ergonomic closures for instances.

ljharb avatar Jul 03 '20 16:07 ljharb

@ljharb fair enough, thank you for the clarification and setting my mind at ease. Apologies for jumping to conclusions.

marlon-tucker avatar Jul 03 '20 16:07 marlon-tucker

and why can't we just use "variables inside a closure"?

javascript in websites are essentially [stateless] programs, message-passing string-data between [externally-stateful] ui's and datastores (possibly behind http-servers). in fact, this sums up javascript-usage outside of websites as well.

i don't see how this [stateful] proposal will improve ergonomics of "variables inside a closure" when i'm writing mostly [stateless] programs message-passing string-data.

kaizhu256 avatar Jul 04 '20 21:07 kaizhu256

@kaizhu256 because class constructors (pre-ES6, or ES6+) don't have a per-instance closure available to prototype methods.

Separately, JS in websites are quite often stateful (cookies/localStorage, for example, are persistent state that virtually every website uses), and there's also PWAs that are fully offline, with long-term persistent storage, that have no server whatsoever. Your summary, I'm afraid, is inaccurate for "all use cases" in any JavaScript environment.

It very well might not help certain coding styles/scenarios - but just like any language feature, if it doesn't help you, you wouldn't choose to use it :-)

ljharb avatar Jul 04 '20 21:07 ljharb

cookies/localStorage are [externally-stateful] datastores that javascript [statelessly] message-pass between.

the same goes for caches. they are external datastores (like sqlite/sql.js/redis) that javascript statelessly pass messages between.

kaizhu256 avatar Jul 04 '20 21:07 kaizhu256

So? They're still JS APIs, that are usable and used in browsers for long-term stateful storage, contradicting your claim.

ljharb avatar Jul 04 '20 21:07 ljharb

they are EXTERNAL datastores and dom-element-ui's. the javascript program you and i write requires almost no stateful-code in order to interact with them.

kaizhu256 avatar Jul 04 '20 21:07 kaizhu256

Redux, Mobx, xstate, vuex, ... There are a lot of popular libraries showing that the "javascript in websites are essentially [stateless] programs" claim doesn't describe the web reality.

nicolo-ribaudo avatar Jul 05 '20 09:07 nicolo-ribaudo

@kaizhu256

If I'm understanding your argument, you're saying that "because the vast majority of the state information that JS/ES manipulates comes from sources external to the language, JS/ES is stateless." I'm sorry, but that simply isn't true. Any time you use 'let' or 'var', you're declaring some state. Since Functions in JS/ES are first-class objects, any time you declare a function, you're declaring some state. JS/ES is in fact one of the most state-full languages currently in use.

Here's another way of thinking about it. If we were to accept your perspective that JS/ES is primarily stateless, then we could use that same argument to claim that programs written in JAVA, C++, C, D, F, and for that matter, any other language, are all equally stateless since they primarily draw information from external sources and write them back to external sources. I think even you would have a tough time buying such an argument.

The ultimate truth is that there is no such thing as a state-less language. There are only languages that allow you to get away with maintaining less state. To create a state-less language, it would need to be written for a state-less processor. All processors are state machines, so that's impossible.

rdking avatar Jul 05 '20 20:07 rdking

This topic has been open for just short of a year. In the OP, I brought up an issue that does not appear to have been "discussed to death". Yet I'm left to wonder if anyone in TC39 even considers it a topic worth discussion. Discussing the technical merits of a particular change is incontrovertibly necessary. However, I argue that discussion of how such changes affect the development community is equally necessary and should not be brushed away with simple hand-waving or assumptions that "most" of the community use the language in the same way TC39 member do. Is anyone from TC39 willing to discuss this issue?

rdking avatar Jul 05 '20 21:07 rdking