Dotty: Meaning of private toplevel definitions

Created on 16 Dec 2019  Â·  12Comments  Â·  Source: lampepfl/dotty

What should the visibility of x defined a toplevel definition like

package p
private val x = e

be ? Currently it is the package p. But we could also decide that it should be the file in which x appears.

high-prio needs spec

Most helpful comment

I always thought that private class means private to the file - possibly because in package objects private means private-to-object. Everyone seems to be using private[p] for private to the package.

All 12 comments

It should have exactly the same visibility as a private class X, obviously. I believe that means only the current file?

No, private class means it's private to the package. But opaque type means opaque to the file.
But maybe one of them should be changed?

Speaking from the peanut gallery -- to me, private to the package seems more intuitively obvious, since my understanding of top-level declarations is that they are generally scoped relative to the package itself. That's not exactly a principled argument, but it's one data point of expectations...

I always thought that private class means private to the file - possibly because in package objects private means private-to-object. Everyone seems to be using private[p] for private to the package.

Hello! Some aggregated thoughts:

In favor of file visibility:

  • Simplicity of notion: "private (without explicit scope) means as private as possible (private to file for top level defs)"
  • Ability to express both file scope (via private) and package scope (via private[p])
  • Now, the notion of "file scope" is partially introduced (e.g. for sailed traits and opaque types). With such a change this notion would be more equally supported
  • Readability: seeing private definition, one can be confident, that all usages are located in this file (no need to perform global usage search)
  • Ability to define hidden auxiliary stuff at the top level (however, one might argue that top level isn't a good place for such a things), e.g:
package p1

// Could be useful within package
private[p1] val data = Santa(Country.CH, "Samichlaus") ::
    Santa(Country.RU, "Дед Мороз") ::
    Nil

// Needed only for 'def santaOf'
private val indexedData = data map (s => s.country -> s) toMap

// Publicly exposed
def santaOf(country: Country): Option[Santa] = indexedData.get(country)
  • After a brief code search by "^private class" regexp, it feels like people usually tried to express file scope by this means

In favor of package visibility:

  • Intention, that really private things should be placed in more secret places
  • Consistency with old package object {private ...} and private class behaviour
  • Probably easier migration to 3.0 (we can imagine people fixing modifiers for package-used but private-defined classes)

Now, I feel 55%/45% bias towards the file visibility for the top level definitions (including private classes). Looks like small but strategically good change.

We have only two choices:

  • private to the package, as is the status quo
  • private to the file, but not visible for any classes and objects in the file.

The reason is that

package p
private val x = e
class C { ... }

is translated to

package p
object ...$package {
  private[p] val x = e
}
class C { ... }

We could drop the [p] qualifier from the private, but then it would not be visible in class C.

Arguments in favor of dropping [p]:

  • Opaque types behave the same way. A top-level opaque type alias is transparent only in the synthetic object that surrounds it.
  • It makes private the smallest scope possible.
  • We can already express package private: Just write private[p].

Arguments against:

  • The exclusion of nested classes is weird.
  • private on toplevel class always means package private, so this would open a discrepancy.

My tentative position is that it's better to leave things as they are. I.e. private is package private. But I'm willing to be convinced otherwise.

Relatedly, inclusion of nested things is weird. Here, f is visible in p in the same file, but not compiled separately. f is in the empty package. private makes it inaccessible in p. This exercise helps me not to take textual nesting too literally.

// ok in one compilation unit
def f = "hello, world"

package p {
  @main def m = println(f)
}

I recently asked about private[C] in relation to privacy, where C is immediately enclosing, and the suggestion was that private is categorically different. So I'm also prepared to write private[p], where p is immediately enclosing, if that is the different behavior I want.

I'm not sure which emoji reaction to use for the poll, but I vote for changing status quo to "wow, access modifiers are really interesting in Scala 3."

As I said in the beginning, to me it's pretty obvious that a private def needs to have the same visibility as a private class.

Regularity wrt the scope of opaque type aliases would be nice, but infinitely les important than regularity with the visibility of classes.

As I said in the beginning, to me it's pretty obvious that a private def needs to have the same visibility as a private class.

It looks like Scala 2 and 3 don't have the same opinion of where a private class should be visible actually:

// A.scala:
package pkg
private class A
// B.scala:
package pkg
class B extends A

```shell
% scalac A.scala B.scala

Scala 2:
```scala
B.scala:3: error: private class A escapes its defining scope as part of type pkg.A
class B extends A
                ^
1 error

Scala 3:

  • Compiles without error, -Xprint:typer reveals that private got typed as private[pkg].

Oops nevermind, I misread the error which is about the private scope leaking, not about A not being visible. So in both cases A is visible in separate compilation units in the same package.

@odersky
What would stop a third option? — conjure a file-private scope distinct from the synthetic object scope. There's no reason for new features to have to translate directly into existing Scala syntax.

(Actually, such a scope already exists for sealed, just not for visibility)

I agree that private defs should behave like private classes, and private classes should behave like Scala 2. That is what is implemented, so no action is needed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

adamgfraser picture adamgfraser  Â·  3Comments

noti0na1 picture noti0na1  Â·  3Comments

odersky picture odersky  Â·  3Comments

liufengyun picture liufengyun  Â·  3Comments

LaymanMergen picture LaymanMergen  Â·  3Comments