zenparsing / js-classes-1.1

https://zenparsing.github.io/js-classes-1.1/
83 stars 3 forks source link

What about let? #25

Open ljharb opened 6 years ago

ljharb commented 6 years ago

Currently, there's no reason to use var ever again (according to all JS styleguides I'm aware of) - it's expected to allow var inside class bodies, of course, but could let also be allowed?

(Obviously const wouldn't make sense unless there was an initializer)

mgtitimoli commented 6 years ago

why the need of prefixing them at all? if going on this direction, why can't we reuse hidden to keep it consistent with the way methods are defined

allenwb commented 6 years ago

An important part of this design is emphasizing that "instance variables" are not properties. They differ from properties in many important ways and we believe that it is bad to lead JS programmers down a path where they don't fully understand all of those differences.

One reason for choosing var is because it can be seen as an abbreviation for "instance VARiable". It places the emphasis on the fact that this is a declarator for instance variables.

Note that the "hiddeness" of instance variables is only one of the many semantics differences between them and properties. Using hidden for such declarations would emphasizes only one of these differences and minimizes the importance of all the others.

Hidden methods are somewhat the opposite story. We have chosen to reuse the existing ConciseMethod syntax. The primary difference between regular concise methods and hidden concise methods is "hiddeness". So, in this case use of hidden emphasizes the key distinction.

ljharb commented 6 years ago

If you have concerns about conflating properties and fields, then I don't understand how it's acceptable to equate lexical variables (which don't belong to an object) to class instance variables (which do).

justsml commented 6 years ago

Thanks for the explanation @allenwb -

On your point here:

"One reason for choosing var is because it can be seen as an abbreviation for "instance VARiable". It places the emphasis on the fact that this is a declarator for instance variables."

Why not use something like local x = 10; - instead of var x = 10; - surely more confusion would come from a new behaviour/usage of var?

justsml commented 6 years ago

how about void x = 10; 😆 ... lets have fun recycling JS keywords now that it's in fashion. ;)

allenwb commented 6 years ago

Actually, my second choice for a recycled keyword is with.

class Foo { with x, y, z; }

futpib commented 6 years ago

@allenwb

An important part of this design is emphasizing that "instance variables" are not properties.

...

Using hidden for such declarations would emphasizes only one of these differences and minimizes the importance of all the others.

Hidden methods are somewhat the opposite story. We have chosen to reuse the existing ConciseMethod syntax. The primary difference between regular concise methods and hidden concise methods is "hiddeness". So, in this case use of hidden emphasizes the key distinction.

Both hidden methods and instance variables are not properties and both are hidden. (Or am I missing something?) Why do you consider "variableness" as a feature of instance variables but not as a feature of hidden methods? (I root for using the same keyword for both, whatever that keyword will be.)

mgtitimoli commented 6 years ago

I keep thinking on what @ljharb said first, if we ever add initializers, at that time it could make sense to have consts (since we are gonna be able to define them), and if so, would it make more sense to let the door open for let (no pun intended :slightly_smiling_face:) to be consistent with the way we are now defining variables?

allenwb commented 6 years ago

@futpib We considered using hidden in place of var but ultimately decided we preferred var. But hidden remains a strong alternative.

One of the issues with using hidden is that you still need to have the instance variable (or some alternative) terminology to take about the actual mutable value holder that is being declared. Unless you just want to call them "hidden variables".

Another factor, hidden seems more like a modifier rather than a describe keyword. Class definition already have ConciseMethod definition syntax. Hidden methods use the exact same syntax but with a leading hidden modifier. The modified (or its absence) is describing how the method is accessed (via a property reference or via a hidden name reference).

in one sense, the full equivalent state declaration should be: hidden instancevariable x, y;

var x,y; seems like the most reasonable abbreviation (and has some history, see various generations of ES4, also Flash).

hidden x, y; remains plausible.

futpib commented 6 years ago

Using either var, let or const will prime people towards using hidden identifiers like variables (e.g. return x;) and (metally) parsing a var (or let or const) declaration will no longer be context-independent, it will have completely different meaning if used insed a class declaration.

hax commented 6 years ago

I think var is a better choice.

var bring me to the old closure private pattern, in that era, we have no let/const. let/const has TDZ, which var (both function var and instance var) has not.

Consider current mainstream ES6+ coding style prefer-no-(function)-var, it's a win-win to use var keyword only for instance var which clearly separate lexcial scope bindings and class instance vars, it could help to eliminate the concern of possible confusion of normal var VS instance var for the newcomers.


About const, even we introduce initializer, it seems readonly is much correct keyword/decorator than const. And readonly keyword/decorator can work without initializer (which just don't allow mutation after constructor call.)

Note, I personally think readonly is a nice-to-have for public field, but not must-have for private/hidden state.


About hidden, I'm ok with it. But I understand the motivation of use only one keyword / not introduce new keyword. I guess token is possible solution.

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

This syntax is not very attractive in aesthetic point of view. Maybe we can consider - ~ !

class A {
  var x
  ~getX() {
    return this->x
  }
}

- ~ ! all convery negative meaning which I think good for "hidden" semantic, I chose ~ in the example for my personal taste, and it will not bring ASI issue if we add initializer.

The only dissatisfaction is ~*generator() {}, consider we do not commonly use generator methods, I think I can live with it 🤪

Jamesernator commented 6 years ago

I suggested class-parameters here as a way to be able to get data into const -> ones but perhaps it's fine in the constructor?


Unrelated to the above but it could be the case that -> is a closure-sharing operator like-thing e.g.:

const externalName = 12

class MyClass {
    // Not shared even with other instances
    const fullyPrivate = 10

    // Shared with other instances that have access to the ->sharedFoo
    // token as 
    const sharedName = 10
    ->sharedFoo is sharedName

    // Creates an anonymous variable that is attached to the current
    // instance, would be pseudo-equivalent to
    // const autoGenName
    let ->x = 10

    // In theory even external variables could work, e.g. for static properties
    ->someValue is externalName
}

This would be very similar to private fields in that every object stores a mapping of private names --> values. But unlike private fields where object.#privateField is effectively a property access (reinforced more so with decorators) object->someName would dereference the variable that's pointed to by the internal mapping. These variable-reference object things though wouldn't be accessible directly in this proposal so friend classes probably wouldn't be doable (but private data on object literals could with little extra work!).

Jamesernator commented 6 years ago

I'm not sure what the outcome was at the recent TC39 meeting but I realised another couple bonuses of having a "closure sharing operator".

First variables just work as variables:

class Counter {
    // localName is count
    let count = 0
    // shared name is ->internalCount
    // just using a bad name to demonstrate it works
    ->internalCount is count

    increment(i) {
        count += i
    }

    // Combine two counters
    combine(other) {
        count += other->internalCount
    }

    getCount() {
        return count
    }
}

Secondly they extend trivially to object literals with just a small syntax addition to object literals:

function counter() {
    let count = 0

    return {
        ->internalCount is count,
        increment(i) {
            count += i
        },

        combine(other) {
            count += other->internalCount
        }
    }
}

There could of course be shorthands for this like:

class Counter {
    let ->count = 0
    // Both count and this->count work
    // if we make ->count declare both the variable
    // and ->sharingOperator at once
    increment(i) {
        count += i
    }
}

...
let count = 0
return {
    ->count,
    increment() {
        count += 1
    }
}
...