Proposal to retain private[this] in Scala 3

I don’t think private[this.type] is less weird than private[this]. Note that access qualifiers are not necessarily types. For example, when you write private[mypackage] or private[MyObject] , mypackage and MyObject are values, not types.

4 Likes

Sorry, my tone didn’t come through well over Discourse.

This was (obliquely) my point: there are plenty of places where term names are put in these (special) square brackets. The real question here is whether (current) private or (current) private[this] is more like what people want to mean when they say private, and I’m not sure if the current semantics are so incorrect as to warrant dropping private[this].

Also, I’d argue that having private mean the same as Java’s private is helpful to newcomers from Java.

1 Like

Just to clarify: the current state is that private[this] is inferred, and fields so inferred will not get accessor methods.

1 Like

One option there is to use @inline on vals to mean “use direct field access”—and would error out if the val can be overridden.

Right – but I think the point is that that’s bad from a maintenance POV. private[this] is, to some degree, future-protection – it’s a statement that this member isn’t supposed to leak outside of this particular instance.

But the inference mechanism here is, frankly, really obscure: I think most engineers will look at it, go “I have no idea why they’re putting this. here” (or just not notice it), and just ignore that, thus defeating the inference. If I’m reading the docs correctly (and perhaps I’m not), one engineer failing to say this. in one place will defeat the inference with no warnings.

I rarely use private[this] myself, so I don’t have much of a horse in this race. But I think there’s a valid point here, that the inferred version is obscure to the point of near-uselessness.

(A possible partial solution would be to issue a warning, or even an error, if there is a mix of this. and not within the class. That’s still too obscure, but at least provides some hope of being maintainable. If that’s already implemented, I’d suggest it be added to the docs.)

4 Likes

I think you’re misreading the doc (for reference, it’s available here).

The compiler now infers for private members the fact that they are only accessed via this .

My understanding is that “accessed via this” is not a syntactic restriction but a semantic one. In other words, the check would be made on type-checked code, where the tacit this accesses are elaborated.

Sorry, how does that break compared to the status quo?

2 Likes

The abnormality more semantic than syntactic. Except for private[this], every other time you see private[X] it’s widening visibility, so private[this] restricting visibility instead is irregular.

2 Likes

private[this] is disallowed for case class fields, because generated equals uses all case class fields.

1 Like

Since this is a case class, we expect it to have an auto-generated equals method that will, if the other object is also a Plus, compare field by field. To do so, it needs to read these fields for a different object of type Plus, which is allowed if they are private, but forbidden if they are private[this].

1 Like

Is this really that common of a pattern? I can’t recall ever seeing a case class with private fields (not saying they don’t exist, mind you), and that’s definitely something I’d flag as a code smell if it came up in a review.

What would be the use case for something like this? If I’m understanding this correctly, private fields would either break the default extractors, or they would allow the values to escape their visibility restrictions

1 Like

Interestingly, it looks like private and private[this] are only respected for case classes for attempted direct access.

case class TestPrivate(private val a: Int)
case class TestPrivateThis(private[this] val a: Int)

object Main extends App
  val t1 = TestPrivate(1)
  val t2 = TestPrivateThis(2)

  val aFromT1 = t1 match 
    case TestPrivate(privateA) => privateA

  val aFromT2 = t2 match
    case TestPrivateThis(privateA) => privateA

  //println(s"Leaking private directly: ${t1.a}")
  //println(s"Leaking private[this] directly: ${t2.a}")

  println(s"Leaking private via extractor: $aFromT1")
  println(s"Leaking private[this] via extractor: $aFromT2")
  
  println(s"Equality check private: ${t1 == TestPrivate(1)}")
  println(s"Equality check private[this]: ${t2 == TestPrivateThis(1)}")
  
  println(s"Inequality check private: ${t1 != TestPrivate(2)}")
  println(s"Inequality check private[this]: ${t2 != TestPrivateThis(2)}")

The commented lines fail compilation, the rest both compile and run:

Leaking private via extractor: 1
Leaking private[this] via extractor: 2
Equality check private: true
Equality check private[this]: false
Inequality check private: true
Inequality check private[this]: false

So equality doesn’t work, because the attempted access is direct, but the fields aren’t actually protected in any meaningful way because a trivial pattern match would allow extracting these fields.

At least in this thread we have proved Martin’s point, that private[this] results in excessive amounts of bikeshedding.

2 Likes

Couldn’t we auto generate existing scala 2 code with private to private[Class] and let legacy maintainers the decision for private[Class] refinement to private.

I just don’t believe private[this] can be inferred, it would presumably only work in limited cases but not for libraries exposing non final classes in their interface.
How does the compiler infer private[this] in such a case?

I still have not seen a single instance of code where the difference matters, i.e. where things would go wrong, if a private[this] was widened to private.

This edit of ConcatIterator removes the private[this] from two methods as obsolete style, and hides the two methods as local defs.

ConcatIterator is a classic case of twincest, with four private vars, which are accessed when it notices that its wrapped iterator is also a ConcatIterator. There have been a couple of bugs in these 60 LOC pertaining to when to set fields to null.

The private[this] on advance and merge limited the number of arrows I had to draw on my napkin at lunch. Is that worth any cost in language complexity?

I am reconciled to coding in the new style, using only private and private[Outer] for modularity, abjuring private[pkg] altogether, that is, Java-style without default access and also enclosing package qualifiers such as private[collection].

Rex noted on the other thread that private constructor syntax is an edge case if it is supposed to mean private[this]. It’s already difficult to reason about the lexical context of constructors.

It’s also strange that class qualifiers are not ordinary names; for example, imported names are not supported.

In brief, object-private and package-qualified private seem to offer flexible design tools, but are really just a crutch.

1 Like

I make a game engine in Scala 2. Some of my classes are large with thoughtful APIs, others are simply large and unwieldy. This seems fairly real world because, like all teams, I must constantly decide to build new game features vs cleaning up old messes or factoring something large into several smaller things.

private[this] is used in about 70 locations in my codebase on an even distribution of defs, vals, and a few vars.

I have found it very useful in practice to read private[this] in my code and immediately grok that this definition was designed for internal use only. And I get a handy compiler error if I accidentally use one of these definitions in the companion or elsewhere.

Would the sky fall if my private[this] was widened to private? Surely not. But, as argued in the original post of this thread, private[this] is a useful design tool and mechanism worth preserving in Scala 3.

Allow me to paste the updated proposal to see if we might come to some agreement on it:

Amended, specific proposal (pasted from above)

  1. The syntax private[this] is dropped in Scala 3
  2. Scala 2’s private[this] becomes private in Scala 3
  3. Scala 2’s private[C] remains the same in Scala 3. i.e. the rules determining which C may be chosen are the same in Scala 2 as in Scala 3
  4. The effect of Scala 2’s private is now achievable with private[MyClassName] in Scala 3, where MyClassName is the class in which the variable is defined.
3 Likes

My game engine makes very infrequent use of object-private and package-qualified private. Just 9 usages compared to 70 for private[this].

However, upon review, object-private and package-qualified private were very useful in these 9 usages.

The amended proposal (previous post of mine just now), based on the suggestions of others, reiterates a crisp way to retain all of this functionality in Scala 3.

1 Like

Can we not use private[This] ??

1 Like

Yes it is, its used when you want to do safe constructors i.e.

case class PositiveInt(private int: Int) extends AnyVal

object PositiveInt {
  def apply(int: Int): Option[PositiveInt] =
    if (int > 0)
      Some(int)
    else
      None
}

This way its impossible (unless you are in the same package as where PositiveInt is defined) to construct an instance of PositiveInt without it being an Option.