js-classes-1.1 icon indicating copy to clipboard operation
js-classes-1.1 copied to clipboard

Document differences from existing proposal

Open bakkot opened this issue 6 years ago • 8 comments

I missed at first glance that this proposal didn't include public fields, and I'm sure there's other things I'm missing too. I think that for the committee members who are familiar with the existing proposal - at least for me, anyway - it would be helpful to have a document detailing the differences in syntax and semantics.


EDIT: These lists are not entirely correct, please refer to updated list below instead.

Using the language of the old proposal, so where I say "private fields" I mean "the thing the old proposal calls private fields and the new one calls instance variables", here's my stab at a list of differences:

  • No support for public fields, static or instance (that is, no additional support beyond ES2015)
  • Private field declaration syntax is var x instead of #x
  • Private method declaration syntax is hidden foo(){} instead of #foo(){}
  • Private field/method access syntax is this->x instead of this.#x
  • No private field initializers
  • static {} blocks
  • Adding private fields to a non-extensible object is forbidden (https://github.com/zenparsing/js-classes-1.1/issues/14 / https://github.com/tc39/proposal-private-fields/issues/69)
  • Accessing a private method does not do a brand check, I think (not clear if ({}->hiddenFoo()) would throw)
  • It is an early rather than runtime error to assign to a private method

Things that are similar:

  • The privacy model, I think - it's not totally clear to me whether nested classes inherit visibility of fields the way they do in the current proposal
  • Support for instance private fields, methods, and accessors
  • No support for static private fields, ~~methods~~, or accessors (edit: I guess static hidden methods are supported, looking closer)
  • Runtime brand checks on private field access (but not private methods?)

Things I am not clear on:

  • What happens if you use the return-override trick to add a private field to an object which already has it? That is, what does let o; class B { constructor(){ return o ? o : this; } } class D extends B { var x; constructor(){ super(); o = this; } } new D; new D; do on the second invocation of D?

bakkot avatar Mar 12 '18 18:03 bakkot

Thanks @bakkot for putting together this list.

First, I want to be really clear about language: it is essential that we not use the term "private field" to talk about instance variables, because one of the primary goals of the proposal is to avoid conflating own properties and instance variables under a blanket concept "fields". A similar argument applies for the term "private methods".

Adding private fields to a non-extensible object is forbidden

We briefly had this in place, but it has been removed. (I just updated the rationale document to reflect the current status.)

Accessing a private method does not do a brand check, I think (not clear if ({}->hiddenFoo()) would throw)

That is correct.

It's not totally clear to me whether nested classes inherit visibility of fields the way they do in the current proposal

They do. As in the private field proposal, everything inside of the class body has visibility of the hidden names defined in the class body.

No support for static private fields, methods, or accessors

Again, we use the term "hidden method". Any method (including accessors) can be hidden, and any hidden can also be static.

What happens if you use the return-override trick to add a private field to an object which already has it?

As is (I believe) the case with the current fields proposal, a TypeError will be thrown if the instance already "has" the instance variable.

zenparsing avatar Mar 12 '18 20:03 zenparsing

First, I want to be really clear about language: it is essential that we not use the term "private field" to talk about instance variables, because one of the primary goals of the proposal is to avoid conflating own properties and instance variables under a blanket concept "fields".

I get why this would be desirable, but I think it isn't something we can actually do. All TC39 has actual authority over is the syntax and semantics of the language, not how people refer to parts of it, and I guarantee people will continue talking and thinking about "public and private fields" no matter what language the proposal uses or how we try to teach it. I'm going to continue using the language myself in the context of this thread, since this list is intended to explain things to people who are familiar with the language of the old proposal, and since#x of the old proposal and var x are pointing at a thing with very similar semantics and use cases which needs some label.


Thanks for the corrections and clarifications. Here's an updated list:

Differences:

  • No public fields, static or instance (that is, no additional support beyond ES2015)
  • Private field declaration syntax is var x instead of #x
  • Private method declaration syntax is hidden foo(){} instead of #foo(){}
  • Private field/method access syntax is this->x instead of this.#x
  • No private field initializers
  • Addition of static {} blocks
  • Accessing a private method does not do a brand check
  • It is an early rather than runtime error to assign to a private method
  • Addition of private static methods and accessors (though because of the absence of a brand check these differ from instance equivalents only in their home object)

Similarities:

  • The privacy model
  • Addition of instance private fields, methods, and accessors
  • No static private fields
  • Runtime brand checks on instance private field access
  • Adding a private field to an object which already has it throws

bakkot avatar Mar 12 '18 20:03 bakkot

So in this proposal, hidden instance methods do not do a brand check, but hidden instance variables do?

ljharb avatar Mar 12 '18 20:03 ljharb

@ljharb see hidden method rationale

allenwb avatar Mar 12 '18 20:03 allenwb

Hi @bakkot , great list. Thanks for gathering it together. I'll use it to briefly state my current positions and point to the issue where this was discussed in more depth. Despite the following criticisms, I am strongly in favor of this proposal.

Differences:

  • Private field declaration syntax is var x instead of #x

See #7 . In the current state of this proposal

{
  var x;

in one context would mean something completely different than

{
  var x;

in another context. I find this terrible.

  • Private method declaration syntax is hidden foo(){} instead of #foo(){}

See #7 again

Both hidden and var are driven by community revulsion at # and even apparently at any sigil at all. My hypothesis is that those who object to any sigil want to use dot alone for access. Given binary expr->name on usage, how much additional revulsion would there be for unary ->name on declaration?

I liked an earlier version that had hidden methods just be concise method syntax prefixed with -> before the method name. I would also put a -> prefix before the declared instance variable name.

The current proposal introduces a sigil, an overloading of the meaning of an existing keyword, and a new keyword. The alternative explained here introduces fewer new syntactic elements, but uses the introduced sigil more often. Would large numbers of people really find the first simpler than the second?

  • Private field/method access syntax is this->x instead of this.#x

Awesome improvement!

  • Accessing a private method does not do a brand check

This makes me uncomfortable, but I understand the reasons for this. I reluctantly agree.

  • Addition of private static methods and accessors (though because of the absence of a brand check these differ from instance equivalents only in their home object)

Accessors also differ from methods in how they are invoked.

erights avatar Mar 12 '18 22:03 erights

I am glad to see more work on these proposals - I appreciate the immense political & technical effort these features require.

Thank you all.

I couldn't see anything in the rationale.md on this: Is it really a good idea to use -> as an accessor? - it's so similar to fat arrow functions () => {}

I assumed skinny arrows would be used for non-lexically bound functions.

💯

justsml avatar Mar 13 '18 14:03 justsml

Is it really a good idea to use -> as an accessor? - it's so similar to fat arrow functions

Yes, we did consider this point, and we also considered "stealing" :: from the bind operator proposal: https://github.com/zenparsing/js-classes-1.1/issues/19#issuecomment-371250625.

zenparsing avatar Mar 13 '18 14:03 zenparsing

Seriously though, >>alright = 'maybe' or |>alright = 'maybe'

Noticed some of that here: https://github.com/zenparsing/js-classes-1.1/issues/19#issuecomment-371263615

Ugh, I keep coming back to: do we really need privates in JS?

I know, there are use cases, and it's cleaner than alternatives - but JS has made it this far w/o this stuff.

One thing I'm really curious about:

What percentage of advocates for the private field/method/members use TypeScript or flow already?

justsml avatar Mar 13 '18 14:03 justsml