Best practices for top-level declarations

I’m often unsure if write some functions at top-level or in a singleton object.

As example, I’ve noted the function generateSequence in stdlib is at top-level, but probably it could be declared as Sequence.generate. So why have you preferred this?

Essentially they are equivalent, but there are some differences:

  • top-level declarations are directly available from the code completion list, so they should be easier to find
  • top-level names can clash easily with other declarations
  • declarations at top-level are simplier since they don’t require to nest elements inside an object.

So in your opinion what are the best practices to choose top level declarations?

4 Likes

This is not by any means a full answer, but one thing to consider would be whether the function is independent of an object. Utility functions (think java.util.Math) should not belong in classes as there is nothing object oriented about them. Another case would be “factory” methods that have the name fo the type they create (from calling source code they are indistinguishable from a constructor call), but those functions need to be used judiciously.

1 Like

The recommended practice is to never use object for creating namespaces, and to always use top-level declarations when possible. We haven’t found name conflicts to be an issue, and if you do get a conflict, you can resolve it using an import with alias.

3 Likes

FWIW I love the way it works in Java. All utility methods may have multiple versions, even the methods in Math have a buddy in StrictMath (which nobody uses, but that’s a different story).

I myself use three different versions of my own pow(long, long), namely checked (throwing on overflow), unchecked (ignoring overflow) and saturated. The best naming I could come up with was SaturatedMath.pow (which is nicely short with a static import).

  • Using saturatedPow would only make sense if I needed to use multiple versions in one class, but I never do.
  • Using multiple packages makes no sense as they’d contains just one file each.

The idiomatic Kotlin way to express that would be import kotlin.math.saturated.pow. Since the Kotlin package structure doesn’t have to match directory structure, there is no issue with creating packages containing a single file - you don’t need to put each of these files into a separate directory.

3 Likes

Isn’t the Kotlin stdlib using object namespacing with Delegates? Also, recently, kotlinx.coroutines introduced Dispatchers for easy dispatchers discovery.

3 Likes

Can you elaborate on why do you think using object is bad for creating namespaces? I didn’t know it’s a recommended practice to avoid it. Is it mentioned anywhere else, e.g. Kotlin Coding Conventions?

During the years developing in Kotlin I moved through these steps:

  • do it as close to java-style as possible, i.e. constants within objects
  • do it the recommended kotlin way with top level declarations for all constants
  • back to objects for non-private constants

In our project we have often cases in which a constant needs to be used in exactly 2 places (server / client). Such constants are easier to deal with in objects. Because they don’t pollute the IDE’s global code completion namespace.

Having said this, when using those constants in code I don’t prefix them with the object’s name. I use the object’s name just for discovery and code completion, and then I import the constant.

1 Like

Because they don’t pollute the IDE’s global code completion namespace.

Don’t ‘top-level’ declarations go in the package’s namespace, not in a global one? (Unless they’re in the default package, of course.)

Yes, but in reality you never use the default package. When people say global namespace they normaly mean package namespace in both kotlin and java.

By this I mean the constants that can be code completed in the IDE without prefixing them with any name.