Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Hard-private vs soft-private #33

Closed
littledan opened this issue Jul 13, 2016 · 141 comments
Closed

Hard-private vs soft-private #33

littledan opened this issue Jul 13, 2016 · 141 comments

Comments

@littledan
Copy link
Member

Should private state have an "escape hatch" like TypeScript private, or be strongly private the way that closed-over values are? Soft-private isn't fully written out the way that @zenparsing has done an excellent job on the current WeakMap-based proposal, but the core would be that "private state" is simply (public) symbol-named properties, with syntactic sugar for those symbols, and possibly some kind of introspection over them. It might be bad for overall language complexity/intelligibility to have both kinds of private state in the language and better to decide up-front what we want. Below, I @-mention people who either made this argument or who I think might be especially interested in the argument.

Soft-private

Soft-private would be syntactic sugar for symbols. In ES6, you can currently write code like this:

let x = Symbol("x");
class Foo {
  constructor() {
    this[x] = 1;
  }
}

With a soft-private language feature, there would be syntactic sugar that looks like this:

class Foo {
  #x = 1;
}

Advantages

  • Possibly easier solutions for friends, decorators introducing private state, etc., since it's just a matter of sharing the symbol somehow (@wycats). Introspection method TBD.
  • One might argue that JavaScript is already so flexible and so much can be subverted that there's little value in trying to give guarantees to private state (@bterlson).
  • From a testing perspective, if tests can reach into private state, then the main code does not need anything extra written inline which should properly be only in the test code (@mhevery).
  • This is a language feature that is explained/polyfillable in terms of other language features, and doesn't create any new sort of different properties that behave differently. Therefore it's a simpler conceptual model (@justinfagnani, @ljharb).

Disadvantages

  • Private state is not really guaranteed private, so no hard guarantees about properties that users may expect if they want security guarantees about how their objects are used (@erights).
  • Platform objects, both within ECMAScript and in embedding environments, contain hard private state. If a library wants to be high-fidelity and just like a platform object, soft-private state does not provide this (@domenic)

Hard-private

Hard-private (the proposal described in this repository currently) is based on state that can really only be accessed by code within the class body. This has been constant across all of the different proposals that have been advanced to TC39, though not what TypeScript provides.

Advantages

  • Hard-private could provide actual guarantees about visibility and more assurances to library authors that their internal state will not be accessed inappropriately, allowing them to build something more like builtin internal slots. (@domenic, @erights)
  • With static blocks that run as part of class definition (currently not concretely proposed or championed), it would actually be possible to build getter/setter functions to share with friends (though this code may be slower, especially as the page is starting up), e.g.,
let get, set;
class Foo {
  #bar
  static {
    get = x => x.#bar;
    set = (x, y) => x.#bar = y;
  }
}

Even without static blocks, you could create a static method which does the exporting, though this is ugly.

  • It might be possible for implementations to do certain kinds of reasoning about private state that they cannot do about Symbols (though the extent is limited by the dynamism of the current proposal) (@sebmarkbage).
  • This is something which, to be efficient, probably has to be built into the language, unlike soft-private which can be done by a transpiler like TypeScript. If there are important use cases, then it might make sense to use our limited syntactic space for something that has to be a primitive, rather than something that a user-level transpiler could use, in the spirit of asking browsers to do what is most important to do in the browser (not that this is a goal that TC39 has agreed on). (may be interested: @wycats)

Disadvantages

  • This is a new language construct, introducing something that's sort of like properties but not quite. That's extra complexity. (@justinfagnani)
  • For decorator integration, we'd need a separate kind of way to declare these private properties, and a special way to refer to them in the added method bodies. Unclear how that would work exactly. Not nearly as flexible as simple symbols, which wouldn't really need much of any special support to add as decorators. (@wycats)
  • If you have a program which declares private state, but you want to reach into the private state from outside either for testing or because you want to consider the private state part of a semi-public interface for mainline code, you need to insert an unergonomic escape hatch in explicitly, rather than just apply the escape hatch the code which uses the private state. (@mhevery)
  • Public properties might be more important than private properties; that's what people use in JS today. It might not be worth it to create all this extra semantic stuff just for private state (@esprehn, @jeffmo).

Thoughts?

@ljharb
Copy link
Member

ljharb commented Jul 13, 2016

Hard-private is fully polyfillable via WeakMap, and I think that the encapsulation gained from hard-privacy is critical, and think the benefit of hard-privacy utterly trumps, thousands of times over, any ability to polyfill in browsers that don't ship WeakMap.

Private state shouldn't be tested or accessible. If you need to test it, it shouldn't be private, and vice versa.

The "new language construct" here is simply exposing a way for users to create internal slots - something that previously only the spec was empowered to do. imo this simplifies understanding and reduces complexity.

@glen-84
Copy link

glen-84 commented Jul 13, 2016

I apologize if this is out of place, but would there be value in supporting both?

Use hard-private (#x):

  • For additional privacy
  • For additional performance (in "lower level" code where this is more important)

Use soft-private (private x):

  • For less privacy and some indirect (reflection-based?) access
  • For somewhat less performance, but a much cleaner and more familiar syntax
  • (could support protected in future, and it would also be possible [AFAIK] to use bracket-based access)

Edit: It would also be compatible with TypeScript, if I'm not mistaken.

@ljharb
Copy link
Member

ljharb commented Jul 13, 2016

That seems a bit confusing, and "soft private" doesn't require additional syntax - you'd just use module-level Symbols as normal public properties.

@wycats
Copy link
Contributor

wycats commented Jul 13, 2016

@littledan I think you covered most of my thoughts on this topic pretty well.

A few additional thoughts:


Fundamentally, "hard private" state means private state on an object that cannot be seen by any objects not expressly granted access to that state. This means that requests for that state cannot be intercepted by installing a proxy, nor by creating a subclass of the original class.

In contrast, "soft private" state means private state on an object that, while difficult (or annoying) to access, can be accessed without an explicit grant. Any attempts to "secure" this kind of state can't possibly work (tautologically).

Most of the proposals for private state, including the one in this repository, work by using lexical scope to grant access to the private names, an elegant approach that eliminates the possibility of snooping through proxies and provides a nice, static semantics that is useful for efficient implementations.

A minor concern: from this perspective, any plausible semantics for protected state must be considered soft-private, since it is necessarily possible to capture a "soft-private" symbol by constructing a subclass:

// this example uses theoretical syntax, but the basic point applies to protected syntax
// modelled in terms of the current hard-private state proposal.

class Parent {
  protected #mine;
}

// in a module attempting to gain access to #mine

class Child extends Parent {
  get mine() { return o => o.#mine; }
}

let mine = new Child().mine; // I now have a generic way to access Parent's #mine
mine(new Parent()); // using it

This means that if we are ever to add support for protected state, we will end up with soft-private state regardless. Given that protected state is highly desired by some members of the committee, starting with hard-private may result in getting both anyway, and I would prefer not to do a piecemeal approach that could end up less coherent than we would like.

(As an example, if we end up modelling protected state on hard-private state, protected state will end up with the same proxy blindness as hard-private state. But given that protected state has no special security reason for this limitation, this would be an unnecessary limitation that could feel incoherent.)


@ljharb said:

The "new language construct" here is simply exposing a way for users to create internal slots - something that previously only the spec was empowered to do.

@littledan said:

This is something which, to be efficient, probably has to be built into the language, unlike soft-private which can be done by a transpiler like TypeScript.

I find these arguments compelling.

A possible approach that could allow us to make progress without making an immediate decision on scarce syntax might be to add support for private state via a meta-property.

class Person {
  private.first;
  private.last;

  fullName() {
    return `${private.first.get(this)} ${private.last.get(this)}`
  }
}

As people have pointed out, this would give transpilers enough core semantics to transpile both hard-private and soft-private immediately, without needing to make an immediate call about which of the two styles we want to grant the most sugary syntax (#foo) to.

If transpilers can efficiently experiment with both styles, this will teach us whether one of the two styles is dominant and whether protected is highly desired (and what its semantics might be). We might also learn that hard-private and soft-private are both very popular, which might mean that we should try to find sugary syntaxes for each.

The nice thing about a meta-property is that it gives us the new language feature that we need for efficient transpilation, putting it on equal footing with symbols both from @littledan's perspective (ability to experiment efficiently using transpilers) and @ljharb's (exposing a feature of spec objects to user-space), decoupling it from the most sugary syntax.

@littledan
Copy link
Member Author

To state my own position: I think we should stick with hard-private. I think it's really valuable to allow programmers to build solid abstractions, just like the platform. This gives library authors more ability to evolve over time, without users depending on implementation details. It's just good object oriented design to have a reliable separation between interface and implementation. As language designers, I think it's OK for us to take an opinionated stance in favor of this sort of good design. When library authors do want to expose things as inconvenient/soft-private/protected parts of the interface, there are currently other mechanisms to do so.

Current mechanisms for soft private state

ES currently has two mechanisms to provide soft-private state:

  • Naming conventions like using an underscore at the start or end of a name. This is the most common. The main hazard is that it's not inconvenient enough, so people might circumvent it more often than library authors would like.
  • Symbols are pretty well articulated for this purpose: we have computed property names which make them more ergonomic to use, so you can use them everywhere a string property key can. You can export or import symbols in modules, and have them in lexically scoped local variables. This is much less common, but my understanding is more people are experimenting with it.

I'm not sure I'm convinced that these two existing mechanisms are insufficient as they currently stand. Any soft-private mechanism would, at its core, have to give you a nice way to access and distribute the symbols, but I have a hard time picturing anything nicer than our current support for symbols in lexically scope variables. Square brackets being ugly might be the main disadvantage, since you have to put them both before and after the symbol name, and because it looks like array/object-as-a-map reference, which might not meet some programmers' mental models. Maybe we could have some syntactic sugar just for this purpose (@@? ..?).

Avoiding using a sigil like #

To iterate slightly on Yehuda's private-state-without-a-sigil proposal, if we want something for private state that won't take up much syntactic space, we could use syntax where basically private. replaces # everywhere. That avoids the thorny question of what private.first would be (an object? what if you change it?) As an example:

class Point {
  private.x = 0;
  change(newX) { this.private.x = newX; }
  retrieve() { return this.private.x; }
}

We could also have private.x as syntactic sugar for this.private.x. This proposal avoids the use of '#', which we might want to save for later, while allowing implementations to do all of the same things that they would do with the currently specified proposal. The spec change would be just in the syntax.

@wycats
Copy link
Contributor

wycats commented Jul 13, 2016

Naming conventions like using an underscore at the start or end of a name. This is the most common. The main hazard is that it's not inconvenient enough, so people might circumvent it more often than library authors would like.

That isn't the main issue. The main issue is that the underscore namespace just becomes another contended namespace. Using an underscore in a subclass means verifying that the superclass doesn't use the same name (or trying to use a name that is unlikely to collide). Adding an underscore property in a class that is meant to be subclassed is therefore a semver-breaking change. In practice, this is sufficiently problematic to cause serious problems in my experience.

Symbols are pretty well articulated for this purpose: we have computed property names which make them more ergonomic to use, so you can use them everywhere a string property key can.

We've been using this style quite a bit in Ember, and I've also been using the TypeScript support for private fields. As a point of information, the lack of good "friend" support in TypeScript causes me to lean on the "soft" nature of the TypeScript approach.

I strongly prefer the TypeScript style to the symbol style, which requires a noisy outside-of-class declaration. It's possible to think of the outside-of-class declarations as "declarations", but it just feels like too much busy-work in practice. (I do it when I'm not in TypeScript because of the problems with underscores, but it's more annoying).


I'm not sure I'm convinced that these two existing mechanisms are insufficient as they currently stand.

I can only speak for my own experience: the underscore mechanism is unusable in many cases, and the symbol mechanism exceeds my ceremony tolerance in many cases. What this means is that it's much easier to be sloppy (and use public fields) than use symbols.

That said, I agree that many of these claims are hard to verify, and I also agree that it's possible to transpile various versions of the soft-private-using-symbols approach. I'd like to attempt that kind of experimentation before settling, as a language, on using # for hard-private.

Since the primary rationale for landing hard-private now (as opposed to experimentation via transpilation) is exposing a language feature that can be efficiently implemented, I argue that we should prefer a less-sugary version of the hard-private feature. That way, we can experiment with sugar-for-hard-private and sugar-for-soft-private via transpilation, and determine which of our various instincts reflects how people will use the feature.

I also think, in general, it makes sense for us to be more cautious about rushing language features that can be transpiled, and instead encourage experimentation via transpilation (as you have astutely argued for re: decorators).

@wycats
Copy link
Contributor

wycats commented Jul 13, 2016

To iterate slightly on Yehuda's private-state-without-a-sigil proposal, if we want something for private state that won't take up much syntactic space, we could use syntax where basically private. replaces # everywhere.

I'm fine with that. You could easily construct the object yourself if you wanted to share it (and perhaps with decorators, you could make it easy to do so):

class {
  private.first;

  get firstAccessor() {
    return o => o.private.first;
  }
}

@littledan
Copy link
Member Author

Which part of the symbol mechanism is excessive ceremony for you? Is it having to declare the variable, use square brackets, or both? Do you have another idea for how you'd like to share the symbol around?

@glen-84
Copy link

glen-84 commented Jul 14, 2016

That seems a bit confusing

Why? They would each have a different name and purpose (private fields/data/whatever vs private properties).

and "soft private" doesn't require additional syntax - you'd just use module-level Symbols as normal public properties.

So now we must define all of our property names outside of the class? That's crazy, and I think that is what @wycats was referring to as being "a noisy outside-of-class declaration" and exceeding his ceremony tolerance. I agree 100%.

If symbols are to be used for soft-private state, they need some sugar, and the TypeScript-like syntax is already well-known and commonly used.

Regarding hard-private, I think that the private.var syntax might be confusing, and appear as either a typo (period instead of space) or a property access on some user-defined object. Perhaps private#var would make more sense in this case. Both options would allow for other access levels (assuming that is technically possible), which are not possible with the current #-prefix approach.

@wycats
Copy link
Contributor

wycats commented Jul 29, 2016

@littledan @tc39/delegates We had a very brief discussion (10 minutes) at the last meeting about private state, and at the time I wasn't comfortable advancing this feature without a longer discussion about soft vs. hard private.

After the meeting, I had a conversation with Mark Miller and @allenwb about hard vs. soft private (especially as they relate to protected and friendly fields), and I think we came up with a path for all of these features that can be based on the private field approach in this spec.

In short, the idea is that names could be "imported" into a class, either from a superclass or a friendly class. Here is some straw man syntax as an example:

const Friends = Symbol();

class LifeForm {
  protected #name;
  #homePlanet;

  export #homePlanet for Friends;
}

class Human extends LifeForm {
  use protected #name;
}

class Spaceship {
  use #homePlanet using Friends;

  warp(lifeForm) {
    setACourse(lifeForm.#homePlanet);
  }
}

This syntax and semantics is still awkward and would want quite a bit of refinement, but it shows that providing a mechanism for exposing (and using) a symbol from a superclass, as well as collaboration between friends, could be accomplished within the rubric of this proposal.

I think that protected (and support for some kind of friend access without hacks) is important, and something that we should try to work out (or reject, perhaps) as we advance this proposal.

In light of this discussion (which we couldn't have during the meeting itself because we were restricted to 10 minutes), I am comfortable advancing the feature to Stage 2. (If I recall correctly, there were several other committee members who also seemed uncomfortable advancing given the short timeframe for discussion, but we should be able to move this fast in September.)

@littledan
Copy link
Member Author

I'm all for figuring out how to make friends with better forms of access. I'll try to iterate on this.

@syrnick
Copy link

syrnick commented Aug 19, 2016

I'd like to make an argument for soft private semantics. In my experience I had to use private state several times to debug or patch critical issues.

In the case of open source libraries, the user has the option of forking the library and removing the hard private declarations. It’s just a hassle.

Hard private makes it difficult to patch bugs for a specific environment that the library maintainer didn’t quite envision. Those issues could be hard to address properly and might require major refactoring that the end user isn't capable of doing.

Reaching out into private methods should definitely be discouraged. Library maintainers are NOT expected to keep private variables the same over patch releases or even within a patch release.

The user should be given static analysis tools bring the code into compliance as much as possible and in rare cases opt out where strictly necessary.

I think it would be fine if accessing soft private properties required some ugly syntax like foo.__private_property__bar ala React's dangerouslySetInnerHTML. That would make it obvious that this is code is suspect.

@glen-84
Copy link

glen-84 commented Aug 21, 2016

foo.__private_property__bar

I hate these magical properties ... couldn't there just be some from of reflection function/class?

@littledan
Copy link
Member Author

@syrnick Given that a library author can already do foo.__private_property__bar, what kind of language feature do you think would make sense to make private state easier to use, but still a hassle to get into? Where's the right "hassle line" exactly, if forking a library is too hard, but foo.bar_ might be too easy (with shadowing from subclasses, using as a public interface, etc)?

@dalexander01
Copy link

dalexander01 commented Aug 26, 2016

I absolutely prefer private.field or this.private.field to #field or this#field.

Is the sigi-less option suggested by @wycats still an option?

@littledan
Copy link
Member Author

@dalexander01 Which sigil-less option--using private. instead of # as the sigil? Yes, that's still possible. Using foo.bar for private state access? I don't see how that's possible, but I'm open to further proposals.

@dalexander01
Copy link

@littledan yep, private.field is what I had in mind.

@littledan
Copy link
Member Author

Oops, @bakkot pointed out to me that this idea makes no sense actually--private is of course a valid property name. We could do something ugly like private(receiver).field but not receiver.private.field.

@syrnick
Copy link

syrnick commented Aug 27, 2016

@littledan the owner of the private properties would access them normally (e.g. via this.foo or this.private.foo). It could be just foo if that can be properly interpreted when foo statically resolves to a private property.

@glen-84 that's a perfect reaction. Any "trespassing" should raise an alarm to the reader.

I think it's ok to reserve "private" in classes that want to use private properties. Only if the class (or a base class) declares a private property, it would be able to access private properties. E.g.

class A {
    private foo;
    getFoo() {
        return foo;
    }
    getFoo2(anotherInstanceOfA) {
        return private(anotherInstanceOfA).foo;
    }
}

@ljharb
Copy link
Member

ljharb commented Aug 27, 2016

@syrnick would private(obj) be a syntax error, or would it return an object containing private field entries? I'd hope the former.

@zenparsing
Copy link
Member

@syrnick Consider also the interaction with public class properties. With your proposal:

class C {
  private foo = 1;
  bar = 2;

  method() {
    this.bar; // 2
    this.foo; // undefined - wth? 
  }
}

The sigil-based proposal works elegantly with all of these constraints:

class C {
  @foo = 1;
  bar = 2;

  method() {
    this.bar; // 2
    this.@foo; // 1 - as expected
  }
}

@amiller-gh
Copy link

@littledan, wouldn't the private(receiver) syntax also break the internet? private is a valid variable name (sadly).

Consider:

var private = "SECRET STRING – DON'T TELL!";

class Foo {
  private secret;
  method(){
    // What is `private` here?!
  }
}

Either a) private ignores normal context lookup (like arguments) and always uses the built-in, potentially breaking existing sites, or b) defers to a private value in the context lookup if it exists, meaning a framework can break the world by doing window.private = 'lol'

In the other issue thread there seemed to be excitement about the -> accessor instead of the # sigil, thought I'd pull it in here as well to make sure its part of the discussion: #14 (comment)

@Ltrlg
Copy link

Ltrlg commented Sep 1, 2016

AFAICT, this is not an issue since private is not a valid variable name
in this context:

  1. private is a reserved word in strict mode
  2. class definitions are always strict mode code

@amiller-gh
Copy link

Well would you look at that, you're right. Ignore that I said anything then!

@indolering
Copy link

class C {
  private foo = 1;
  bar = 2;

  method() {
    this.bar; // 2
    this.foo; // undefined - wth? 
  }
}

That is, at most, a one off mistake.

@indolering
Copy link

WRT testing: there is an advantage to allowing access to private fields. Yes, you can theoretically test every code path but it's a lot of extra work. I'm pretty ignorant to how these features are actually implemented, but would it be possible to allow access based on a runtime switch or a macro so we could enable access during testing?

@ljharb
Copy link
Member

ljharb commented Oct 8, 2016

If you need to test it, it shouldn't be private.

@esprehn
Copy link

esprehn commented Oct 8, 2016 via email

@gandazgul
Copy link

@bakkot if someone is using Reflection to get to a soft private field then they can suffer the deprecation and change their code, there has to be a limit to the hand holding we do with bad practices.

@bakkot
Copy link
Contributor

bakkot commented Jun 16, 2017

@gandazgul

A lot of people would say exactly what you're saying about people who are willing to violate the convention that _-prefixed names are private. I'm not sure how exposing things through reflection is any different.

Anyway, per FAQ:

[Library authors] do not generally consider themselves free to break their user's pages and applications just because those users were depending upon some part of the library's interface which the library author did not intend them to depend upon.

@ljharb
Copy link
Member

ljharb commented Jun 16, 2017

If it's accessible, it's public. "soft-private" is no different than underscores, because both are a convention (albeit, the former would be a convention baked into the language).

If a user can do it, they will, and if you break them, you broke them, even if they did something unwise.

@glen-84
Copy link

glen-84 commented Jun 17, 2017

"soft-private" is no different than underscores

Why do you guys keep saying this?

Let's compare:

Underscore: I can access the property with no errors or warnings, I might not even know about this convention, there will seldom be any documentation about the convention (within the context of a specific project), and it "just works".

Soft-private: I cannot access the property, I might see errors or warnings (or just nothing), I can easily find out that a hash prefix (puke) means that it's private and should not be accessed in general code. If I learn how to access it via some obscure reflection mechanism, then I've taken extra steps to do so, written additional lines of code to access the value, and have probably seen warnings about appropriate use cases.

Please stop suggesting that they are the same. They're not.

If a user can do it, they will, and if you break them, you broke them, even if they did something unwise

If I was a library author, I wouldn't give a flying f*ck if someone accessed the private state of my code using reflection, and I then decided to refactor that code, thus "breaking" theirs. How often do you see similar complaints from other languages like C#, PHP, Java, etc.?


Regardless, it's almost pointless discussing this, because the decisions have already been made. The majority of developers think that the sigil looks terrible, but yet you're still moving forward with it under the same classification.

@littledan
Copy link
Member Author

I disagree with @ljharb -- it is qualitatively different, in that you're much less likely to get into the situation by accident. You have to go and do the less ergonomic thing to get there. This makes soft private somewhat useful--in practice, people would be copying a recipe, or knowingly going around to get there, rather than just using some kind of tab completion. The real downside is that it's missing another ingredient that would be useful for many library authors.

If I was a library author, I wouldn't give a flying f*ck if someone accessed the private state of my code using reflection

I'm not sure all library authors have that luxury. Node has had to revert changes which were about things that definitely look like unstable implementation details, and documented as such, when popular-enough libraries end up depending on them. For that reason, @bmeck expressed a preference for "hard private".

@bmeck
Copy link
Member

bmeck commented Jun 17, 2017

I will state that they were doing very strange things at times to get to internals. I do not thing "less ergonomic" would be a mitigating factor to any real degree.

@bakkot
Copy link
Contributor

bakkot commented Jun 17, 2017

How often do you see similar complaints from other languages like C#, PHP, Java, etc.?

You see similar complaints from other languages all the time. In fact one of the major new features in Java 9, modules, has as one of its main goals strong encapsulation in (as I understand it) exactly the sense discussed here: allowing libraries to hide internals even from reflection. They went through much the same debate we've been having here (and have been having since at least 2010).

This was requested by library authors for exactly the same reasons library authors requested strong encapsulation in JavaScript: for example, JUnit 5 came about in large part because

creators went as far as using reflection (down to private fields) to access information that the API would not hand out. This bound tools to implementation details, which in turn made it hard for JUnit’s maintainers to evolve it – nobody likes breaking downstream projects. This is an important reason for why JUnit’s progress has basically come to a halt.

Likewise, maintenance of the Java platform itself had become difficult because of people depending on internals through reflection.

@bmeck
Copy link
Member

bmeck commented Jun 19, 2017

If other languages are having problems with soft-private state and JS already has soft-private via Symbols; are there reasons to not want hard-private? Some statements above seem to be afraid of accidental leakage, but that is something that the programmer is leaking by writing a leak. To my knowledge, no attempt at inheriting the private field namespace for an instance is in the works so things like protected would not be able to use private fields (which I would be happy about personally).

@syrnick
Copy link

syrnick commented Jun 20, 2017

@bakkot I'd like to add the beginning of that quote about JUnit5:

Part of JUnit’s success comes from its great tool support,
for which tool creators went as far as using reflection

That is an essence of open software - assuming that users will have imagination and come up with use cases that authors have not imagined and the interfaces exposed might be insufficient. That's why closed source software isn't fun even if you have an API to it. It's fully-encapsulated.

@littledan
Copy link
Member Author

Well, given what was described above, maybe it would've been better if JUnit thought things through and wrote an explicit interface for tool authors. This would have the best of both worlds--tooling and maintainability. And with open source, if the previous library maintainers don't want to add this, someone can fork it and add such an interface.

I'll mark this bug as closed. Unfortunately, we haven't come to complete agreement here, but there are some really compelling arguments in favor of maintaining a strong privacy boundary.

@robbiespeed
Copy link

Perhaps if we could have dynamic access like I suggest in #104, it would provide a happy medium for those wishing to see soft private.

@fwienber
Copy link

fwienber commented Jun 13, 2020

It seems to me that in many posts here, "soft-private" through Symbols and TypeScript-private are used as synonyms. I'd like to emphasize that they do not have the same semantics!
TypeScript uses private just for compile-time checks. At run-time, in plain JavaScript, private fields are simply public. Nothing of the private modifier remains in the generated code. This means

  • Any JavaScript code or @ts-ignored code can access "private" members directly
  • My main concern: subclasses can run into unintended name-clashes with "private" members. While this leads to a TypeScript compile error, it still means any change of "private" members in a framework class that may be subclassed is a breaking change. Combine this with the missing final class concept, and you are in trouble.

In comparison, "soft-private" is just missing hard encapsulation, meaning you can still access soft-private members through reflection at run-time.

So we actually have three levels of class member privacy:

  1. to prevent direct access at compile-time (private modifier in TypsScript)
  2. to prevent direct access at run-time, but allow access by reflection ("soft-private")
  3. to prevent any access at run-time ("hard-private")

For my use case, I used "soft-privates", defining Symbols outside the class for private members, but find this solution cumbersome to write, and IDE support is lacking (no "go to definition" in WebStorm, not yet tested in other IDEs).
I refrained from using the # syntax, because the compatibility and run-time-overhead issues of the WeakMap polyfill for hard-privates cause me headaches, and even worse, TypeScript only supports # syntax for instance fields, neither for methods (I know arrow functions can be used as (ugly) workaround), nor for static members.

What I, as a library / framework author, need is exactly syntactic sugar for "soft-private" members, instance as well as static, fields as well as methods. This seconds some of the opinions uttered in proposal-class-fields issue 189.
Since the private modifier is already used in TypeScript for compile-time-only checks and is unlikely to change semantics, I was hoping the # syntax to be exactly what I needed, but it seems to be decided to represent hard-private semantics. Too bad.

@trusktr
Copy link

trusktr commented Sep 7, 2020

No description provided.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests