Skip to content

Hidden members in object literals? #8

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

Closed
allenwb opened this issue Feb 3, 2018 · 15 comments
Closed

Hidden members in object literals? #8

allenwb opened this issue Feb 3, 2018 · 15 comments

Comments

@allenwb
Copy link
Collaborator

allenwb commented Feb 3, 2018

It would seem that instance variables and hidden methods from #7 could be easily adapted to object literals. Consider:

function makePoint(x,y) {
   return {var x: x,
           var y: y,
           get x() {return this->x),
           get y() {return this->y),
           set x(v) {if (this->isMutable()) this->x = v},
           set y(v) {if (this->isMutable()) this->y = v},
           hidden isMutable() {returnDate().getDay() == 2),
           ...
   }
}

or alternatively

function makePoint(x,y) {
   return {->x: x,
           ->y: y,
           get x() {return this->x),
           get y() {return this->y),
           set x(v) {if (this->isMutable()) this->x = v},
           set y(v) {if (this->isMutable()) this->y = v},
           ->isMutable() {returnDate().getDay() == 2),
           ...
   }
}

I think this might be a further selling point to this approach. Programmers who prefer object literals to class definitions also get functionality of hidden state and methods .

@zenparsing
Copy link
Owner

Great question!

I've been pondering this all afternoon and I'm still not confident in my answer. I'll have to think on it a little more.

For now, I'll make the following observations:

  • As far as I can tell, the only semantic benefit for using this syntax in object literals would be to have access to super in shared, hidden code. Otherwise, programmers that prefer object factories over class definitions can already use closure over lexical variables and functions to achieve the same results (arguably in a more readable form). My guess is that super usage within object literal concise methods is quite rare.
  • There are probably already enough differences between object literals and class bodies to justify a difference here.
  • I would like to keep instance vars conceptually distinct from properties (and hidden methods). Adding an instance var syntax to object literals seems to blur that distinction somewhat.
  • We could always add this in a later revision.
  • On the other hand, it would seem to be a nice simplification if hidden elements generalized to all objects.

@zenparsing
Copy link
Owner

Another option would be to allow hidden methods, but not instance variables, in object literals.

We would then have an interestingly isomorphic relationship between class definitions and object factories:

function makeObject() {
  let a, b;
  return {
    someMethod() {}
    hidden helperMethod() {}
  };
}
class C {
  var a, b;
  someMethod() {}
  hidden helperMethod() {}
}

This would also preserve the clear distinction between instance variables and hidden methods that I like.

@allenwb
Copy link
Collaborator Author

allenwb commented Feb 5, 2018

For now, I'll just say two things:

I don't think it's about super. People just prefer object literals over classes (or are dogmatically opposed to the existence of class in JavaScript). They still could used hidden private state and internal procedural decomposition that has access to that state. Having the same functionality available to both class and never-class camps would be a strength that the current class proposal doesn't offer.

There is a strong semantic parallel between object literals and the static side of class definitions. They both essentially singleton object definitions. What ever we do should maintain that parallelism as much as possible

@zenparsing
Copy link
Owner

I think I've convinced myself that hidden methods should be allowed on object literals. The same arguments that motivate hidden methods in classes also apply to anywhere else we can find method definitions. The argument is:

It should be possible to transparently decompose methods without altering the observable interface of the object upon which those methods are defined.

I'm still less certain of whether we should allow instance vars on object literals.

@allenwb
Copy link
Collaborator Author

allenwb commented Feb 5, 2018

Any place you (declaratively) define an object it should be possible to choose which part of its encapsulated state is protected from external tampering.

@zenparsing
Copy link
Owner

I think I can add some additional support to the case for hidden methods in object literals. Hidden methods in object literals enables secure, transparent method decomposition for object literal mixins:

let mixin = {
  hidden helper() { console.log('hello') },
  sayHello() { this->helper() }
};

Object.assign(obj, mixin);

obj.sayHello(); // 'hello'

@allenwb
Copy link
Collaborator Author

allenwb commented Feb 5, 2018

Yes, cool. But that won't work if the mixin needs to access instance variables.

For that you need:

let mixin = sup => class {
  var msg;
  hidden helper() { console.log(this->msg) },
  sayHello() { this->helper() }
  constructor() {
    this->msg="hello";
  }
};

let obj = new class extends mixin() {};
obj.sayHello();

@erights
Copy link
Collaborator

erights commented Mar 12, 2018

Whoa, @allenwb I just fixed the indentation in your comment above. I had no idea git lets me edit your comments. Scary!

Did you get notified that someone altered your comment?

@erights
Copy link
Collaborator

erights commented Mar 12, 2018

If hidden state in object literals is per object, rather than per enclosing declaration thing as in #7 (comment) , what utility does it provide over lexical variables?

Btw, I remain fascinated by this issue, but I do not actually advocate #7 (comment) . The argument I make there is "too clever".

@erights
Copy link
Collaborator

erights commented Mar 12, 2018

This example of hidden methods is really about trying to regain some of the ground we lost when infix :: died, right? Is there anything going on here other than that subset of the functionality of infix :: ?

@erights
Copy link
Collaborator

erights commented Mar 12, 2018

What if we get rid of hidden methods, allow lexical functions inside classes (even if it is only functions), and re-introduce infix :: ? Granted this seems a bit more complicated than the current hidden methods. But it does not surprise when the left operand of -> is an unrelated object, and :: gives us lots more functionality at a small differential cost.

@littledan
Copy link
Collaborator

littledan commented Mar 12, 2018

I've spent some time thinking about this topic. I actually included this in an early draft of the "unified class features" explainer, but later removed it. The conclusion I've come to is that hidden methods and instance variables in object literals would be great, but there are two use cases:

  • Where the private name is created separately per evaluation of the object literal (I think this is what you're discussing above).
  • Where the private name is shared among a bunch of object literals and accesses which take place outside of objects (e.g. a "functional" family of exported functions which deal with object literals, which wants to hide internal state).

(There are some use cases for instance variables in object literals that will never work, such as in a Bootstrap-style class system. We'll just have to take this as a sunk cost as a result of several other goals that are shared among the instance variables and private fields proposals.)

Recently, a few people including Justin, Axel and Yehuda have been discussing how to move forward on private name declarations outside of classes. This is the core of addressing the second case. I'd rather not add instance variables/hidden methods to object literals until we have this worked out, since I think the second case is very common and important (unlike for classes, where you can generally get by without that).

@zenparsing
Copy link
Owner

What if we get rid of hidden methods, allow lexical functions inside classes (even if it is only functions), and re-introduce infix ::?

That was exactly the solution that I presented in Paris in 2015. If I recall correctly, there was significant push-back against using function declarations as internal methods in this way. At the time (and I believe this is still the case), there was concern about relying so heavily on this outside of a proper method context. It was argued that such "profligate" this usage would be confusing to users that already find this a bit confusing.

There are other advantages to using hidden methods over lexical function declarations:

  • We avoid having two kinds of function definition forms in class bodies.
  • Users can easily refactor from normal method code to hidden method code, even in the case of accessors.
  • Hidden methods can contain super references, correct for the containing method placement (either prototype or static).

:: gives us lots more functionality at a small differential cost.

The added functionality is the ability to support "extension" methods outside of class contexts. I would still like to see us explore the extension method idea, but in a way that does not run afoul of the concerns above.

@allenwb
Copy link
Collaborator Author

allenwb commented Mar 12, 2018

Recently, a few people including Justin, Axel and Yehuda have been discussing how to move forward on private name declarations outside of classes.

This is just one example of what the Max-min Classes 1.1 proposal is reacting to. TC39 currently has an continuing stream of actual and potential class related proposals, each having to fit in with already committed proceeding proposals. (I call that "kick the ball down the field design") Instead we suggest that we identify and provide the most minimal set of essential functionality that is still missing from class definitions. Then stop! Be finished with classes for the next 10 years.

JS has always been a minimalist language.

@zenparsing
Copy link
Owner

Closing in preparation for public review. Please feel free to continue discussion or open a new issue for a specific topic.

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

4 participants