tc39 / proposal-private-fields

A Private Fields Proposal for ECMAScript
https://tc39.github.io/proposal-private-fields/
317 stars 19 forks source link

Alternative approach: make everything private but prototype properties #89

Open drpicox opened 7 years ago

drpicox commented 7 years ago

The idea is close to the approach of ES5 of "strict": it changed the behaviour of Javascript but just in functions or files.

For example, imagine that we can do something like:

strict class Point() {
  constructor(x = 0, y = 0) {
    this.x = +x;
    this.y = +y;
  }

  get x() { return this.x; }
  set x(value) { this.x = +value; }

  get y() { return this.y; }
  set y(value) { this.y = +value; }

  equals(p) { return this.x === p.x && this.y === p.y; }

  toString() { return `Point<${ this.x },${ this.y }>`; }
}

This example would be translated in exactly the same presented in the introduction. Note that functions, getters and setters are present in the prototype, so they are public, but values for x and y are in this, so they would be private.

Considering something like:

strict class Animal {
  constructor(name) {
    this.name = name;
  }
  getName() { return this.name; }
  setName(name) { this.name = name; }
}

Using this would be something like:

let savio = new Animal('savio');
savio.getName(); // 'savio'
savio.name; // undefined

Something remaining to decide is what to do if there is an assignation:

// option A: assigns it to a virtual middle object
savio.name = 'rico';
savio.name; // 'rico'
savio.getName(); // 'savio'
// option B: throw an exception
savio.name = 'rico'; // Uncaught ReferenceError: savio.name is not defined
savio.foobar = 'rico'; // Uncaught ReferenceError: savio.foobar is not defined

I bet for B, because is more in the line of the philosophy of this solution. If you want to add metadata, please use a WeakMap or similar approach.

Philosophy of the solution

The idea is that private variables are great for encapsulation. Closures are also great, but you cannot access to other instances privates. That is the reason why privates are great.

Usually it is considered bad practice to expose class internal representation. Make some properties private and some properties public drives to bad practices. Making all private but prototype properties effectively promote good practices.

You can expose some internal state through getters and setters (they are defined in the prototype). It ensures that you can effectively control manipulations to your class internal state.

It is not bad to force all properties to be private, you can always use legacy classes and legacy objects. So you can get rid of private limitations, if you need (for example because of performance), just using legacy classes.

It keeps Javascript very similar to the one that we know. Even better, we can port code to the new implementation as easily as adding the strict keyword in front of the class definition. Betting for the option B we should also be able to catch easily legacy parts accessing to the internal state.

Addendum

I am aware that strict is not a keyword, but I love it. It fits very well: we are limiting what we are able to do like it happened before in ES5.

But we have other keywords suitable, like public. It might be funny to see public class Point { ... }.

I also have designed it all taking into account most work done with the current specification, so it could be reused. Semantics should be very similar. It is close to replacing all this.that by this.#that , except for the ones that are in the prototype (which require a fallback for the prototype) and option B that would be ideal for this proposal (note that option A is the current).

Other considerations

You can also define private functions:

strict class Point {
  constructor(x = 0, y = 0) {
    this.x = +x;
    this.y = +y;
    this.cabMagnitude = () => {
       return Math.abs(this.x) + Math.abs(this.y);
    };
    …
}

or:

strict class Point {
    …
  cabMagnitude = () => {
    return Math.abs(this.x) + Math.abs(this.y);
  };
}
littledan commented 7 years ago

I believe JavaScript classes are already used so broadly that we can't go back and change its semantics. There is very broad use of public own properties in the JavaScript language.

drpicox commented 7 years ago

I think that you have not understood my proposal. Please, give me few days that I will write a simple wrapper in plain Javascript (no babel required) that would do the same job:

const Animal = strict(class {…});

Ops. It does not. I need precompile to know when I am exporting this outside the functions and when I have a complex input with other thisses of the same class.


addItself(list) {
  if (!list.includes(this)) {
   list.push(this);
  }
}
rwaldron commented 7 years ago

@littledan this proposal doesn't affect any existing code, I think you overlooked the strict keyword (please correct me if you did and have an argument to counter).

drpicox commented 7 years ago

I think the same @rwaldron but probably @littledan has a more broad view of privates and lot of hard work about its implications.

In fact I found a kind of implementation of my last example here: https://www.npmjs.com/package/private-class

littledan commented 7 years ago

Sorry, I did miss the strict keyword. Previously, an idea has been proposed of const classes--this seems related. Syntactically, it seems like it should work just fine--async isn't a keyword either, but if you put it in the right places it can act like one.

Do strict classes as proposed here need to be part of the private fields proposal, or could they proceed as a separate proposal?