Private in Kotlin


#1

Hi,

A common use case for me is that there is a package that contains functionality to interface with the contents of a package (and it’s subpackages). That could be interfaces, factories, enums, or classes that are required in those interfaces. Then, there are a number of sub-packages that deal with specific implementation details that need not be of any concern to other packages. Ideally, other packages should never need to know the implementation details in those subpackages. They also shouldn’t need to instantiate classes from those subpackages. Let me show an example:

parser
±- public ConfigParser
±- public ConfigEntry
±-xml
  ±- XmlParserImpl
  ±- DomReader
±- json
  ±- JSONParserImpl
  ±- LineBeautifyingSplitter

So basically, the idea is that you only need to look into the main package parser to get a high-level understanding of the functionality. You only need to look into parser.json if you’re desperate to know how the mechanics for the JSON implementation work. All of this is too small to be considered to be it’s own module (jar file). In fact, in most cases, this parser package might only be an implementation detail of a module without any significance to other modules. This is one of the beefs that I’m having with Java for a long time: it’s just not possible to model this (IMHO common scenario) with the access modifiers available.

I just noticed in the release notes for M8 the info about private and that Kotlin seems to take a completely different approach: The xml implementation would be able to see private classes/functions of the parser package but not the other way around. I was wondering about the reasons to design private the way it is designed in Kotlin? What are the use cases you have in mind for the visibility of private elements in subpackages? And even more importantly, what would be the Kotlin way to design the parser package shown above?


#2

We wanted visibility rules to be simple and consistent.

So “private” means that member will be accessible on the same level as declaration and all nested declarations. Top-level private function is accessible at the same package and all sub-packages. Class private function is accessible in the same class and all nested classes. It is more natural than Java approach, because packages have logical semantics of nesting into each other, and their visibility scopes are nested in Kotlin, too. We also have “internal” visibility, which is assigned by default, if you omit visibility keyword: it means that member is accessible from the same module, but not from other modules and library users.

Use case for private visibility of package members is the following: you may have some utilities for implementing interface, which are not interesting to interface users. You make it private.

I think that the most clean way for implementing your functionality in Kotlin is the following:

parser
±- public ConfigParser
±- public ConfigEntry
±- (some public facade for creating parser)
±- impl
  (private classes and functions which are common for implementations)
   ±-xml
     ±- internal XmlParserImpl
     ±- private DomReader
   ±- json
     ±- internal JSONParserImpl
     ±- private LineBeautifyingSplitter


#3

Thanks for the explanation!

I didn’t think of private for shared code that is only required for the package, but of course that is a common use case. So basically there is no way to prevent XmlParserImpl from being accessed by other classes in the same module. That would have been great because in I found that the rule “do not access impl subpackages that are not your own” is hard to enforce in a team.


#4

You can make all members of impl package private, but provide internal factory. Would it solve your problem?


#5

So, the xml package would have an internal factory to create an instance of type XmlParserImpl with everything else being private? I guess, as long as there is no need to access implementation details from the API classes, you would be fine for the specific case of creating instances.

The price to pay would be code duplication: one public set of methods in the API (in the parser package) and one internal one for the implementation that does the actual work. In a way it is similar to friend packages in Java (which are quite clumsy to handle). The approach still leaves the original problem: classes accessable in the whole module that shouldn’t be accessible.


#6

There are always some trade-offs, especially in language design.

We could make either very expressive and complex visibilities system, or easy-to-understand, but less expressive one. We chose simpler.