Kotlin appears to be making a push for using top-level properties and functions, so I’m trying to get a feel for them. After some frustration, I have an insight that helps me think about top-level scope. I thought I’d share that insight – or at least present it for correction.
It seems that the top level scope is analogous to class scope. That is, I can expect visibility within the file to behave as if the contents of the file were wrapped in its own class.
Am I correct in this thinking? I’m trying to figure out how to think about files and top-level members so that I can properly design for them.
My frustration started with trying to create top-level factory methods for classes that should not be instantiated outside of the file. After all, I was supposed to be using top-level methods instead of static methods in Kotlin, where possible, right? I tried to make the class constructors private or protected and yet use them in the top-level methods. But that doesn’t work. I had to create a companion object for the factory methods instead.
How should I think about file scope in general? Thanks for your help!
Private top level functions are only accessible from within that file. Public once are accessible everywhere and they are scoped within the package.
package foo.bar
fun test() = TODO()
// different file
foo.bar.test()
So in that way I don’t think the class analogy fits for public functions perfectly. For private functions it’s spot on.
Also if you look at the generated bytecode, kotlin creates a class for each file putting all top level functions and properties into that class as statics. So looking at it that way you analogy is perfect
I normally think of top-level functions as if they belong to the package, as that is the scope from which they are accessed in kotlin. That said, I put top level functions normally in the file with the classes they work with or if they are grouped differently (like a collection of maths functions) into their own file.
File scope on the other hand isn’t that important to me. I use it a lot, but since it’s only relevant for private utility functions I don’t really think about it. Same way you don’t think about the scope of any other private function you create just to structure your code.
Also I try to never use private top level properties with backing fields. Well ok, I try to not use any global variables if not 100% necessary, which is quite rare.
As for factories I tend to use companion objects. That is one of the ways they are intended to be used (if I remember correctly).
It’s more like if all file-level functions and properties are wrapped in a separate class, while file-level classes aren’t. In fact, they’re visible in Java exactly like that: the class is called FileNameKt. Even if your code is 100% Kotlin, you still use that fact when you specify the main class if your main() is file-level (which it usually is).
I would suggest to do not focus too much on the way how compiler represents this in byte-code. Just try to not think in Java terms> Kotlin is a very different language. Just use its own constructs and think in them when designing your code.