Skip to content

Meaning of private toplevel definitions #7780

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
odersky opened this issue Dec 16, 2019 · 12 comments
Closed

Meaning of private toplevel definitions #7780

odersky opened this issue Dec 16, 2019 · 12 comments
Assignees
Milestone

Comments

@odersky
Copy link
Contributor

odersky commented Dec 16, 2019

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.

@sjrd
Copy link
Member

sjrd commented Dec 16, 2019

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

@odersky
Copy link
Contributor Author

odersky commented Dec 16, 2019

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?

@jducoeur
Copy link
Contributor

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...

@neko-kai
Copy link
Contributor

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.

@ashamukov
Copy link
Contributor

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.

@odersky
Copy link
Contributor Author

odersky commented Nov 24, 2020

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.

@som-snytt
Copy link
Contributor

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."

@sjrd
Copy link
Member

sjrd commented Nov 24, 2020

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.

@smarter
Copy link
Member

smarter commented Nov 24, 2020

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
% scalac A.scala B.scala

Scala 2:

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].

@smarter
Copy link
Member

smarter commented Nov 24, 2020

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.

@neko-kai
Copy link
Contributor

neko-kai commented Nov 27, 2020

@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)

@odersky
Copy link
Contributor Author

odersky commented Dec 2, 2020

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.

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

No branches or pull requests

10 participants