Extension function documentation

https://kotlinlang.org/docs/extensions.html#scope-of-extensions

Says:

In most cases, you define extensions on the top level, directly under packages:

This misled me in a way that took me a while to recover from. While this statement may be true in the Kotlin language itself, I almost always define extension functions in the scope of another type (usually an interface.)

When I first read about extension functions and saw this statement, I was repelled by the idea because top-level extension functions

  1. are static (there is no chance for polymorphism or extension)
  2. are always cluttering the namespace in the IDE (they will always be offered as completions)

I suggest changing this line to something like this:

If an extension function is declared at the top level, directly under packages:

then it can be used outside its declared package by importing it at the call site:

I’m not sure if I understand you correctly. The main point of extension functions is to have static utility functions, but scoped to a specific type. It sounds really weird to me to declare them inside some interface, because it would make them very hard to use. Could you provide some example?

One exception is extension functions that are members. This is rather rare, because, as I said, such functions are harder to use. But it could be useful in some cases like DSLs. Still, documentation says that extensions are top-level in most cases, not always and this is true.

1 Like

Currently, there are two types of extensions, which work a little bit different:

  • Top-level extensions. Those are declared in files and work everywhere with import. They are dispatched statically.
  • Member extensions. Declared in classes and interfaces. They work in dedicated class scopes, do not need imports and actually dispatched dynamically, and could be inherited.

Both variants are useful. Member extensions have a much narrower application in builders (DSL if you like) and more esoteric APIs.

In the future, the multiple receivers (aka context receivers) will bring both usages together and will allow declaring statically dispatched context-bound extensions.

1 Like

Almost all of the extension functions I’ve written are members (usually of interfaces with no abstract members) and this is not for DSLs.

I do this for two reasons that I mentioned in the OP. OOP (polymorphism and extensibility) and not cluttering the top-level namespace in the IDE.

I do not find them difficult to use at all. When I need these extension functions, I either implement the interface or use something like with(RatpackScope) { ... } where that identifier is the companion object of the interface.

The current wording

In most cases, you define extensions on the top level, directly under packages

Seems inappropriate to me in several ways and is very easy to improve. Even just replacing “In most cases” with “Sometimes” would fix it.

That’s my simple and, I think, reasonable suggestion.

Strictly speaking, top-level functions (and properties) only clutter the namespace for the package they’re in, don’t they?

1 Like

Ok, I think I get your point. Still, you need to understand that this is the very first time I see anyone does this. Kotlin stdlib does not do this, other big libraries from Kotlin authors don’t do such things, none of 3rd party libs I ever used did this.

Decluttering the namespace in such a way seems to me like an idea “imported” from some other language, it isn’t really a Kotlin way and it may be even considered a bad practice. I personally like the idea to use such technique for OOP reasons, but would probably limit it to some rare cases only.

But well, this is just my opinion :slight_smile:

I think OP means cluttering the namespace when using IDE. Code completion uses extensions from the whole classpath, restricted to the receiver type only.

1 Like

As @broot pointed out, in actual public Kotlin code, this is the actual practice. It is considered good practice in Kotlin. Member extension functions declared on objects like you do seem very strange to me and I would most likely use them only in very specific contexts. So, if the vast majority of Kotlin developers use top-level extensions most of the time, I find the existing wording absolutely appropriate and desirable, while your proposed wording would be quite misleading considering the entirety of existing Kotlin code.

I don’t find #2 to be a problem here. They don’t clutter the global namespace, only the completion for the type of their receiver, which is exactly the point. Top-level functions that are not extension functions are different, and can be considered bad practice because they clutter completion of all top-level stuff, but top-level extension functions are definitely great and useful.

That being said, you still need to be careful when defining public top-level extension functions. They need to be relevant to most people using the receiver type. A good guideline is to declare public extension funtions only if it would have made sense to declare this function on the type itself. The “utility” extension functions that are just useful in a file can still be declared top-level, but can be private to the file (same applies if we replace “file” with “module” and “private” with “internal”).

There really is nothing about the fact that a function or property is an extension that dictates where it should live, but in practice much of the use cases for extensions means that it makes sense to declare them at the top level. Generally, if it is public and will be used in multiple places this would be the case. But for private ones that are only used in one place I will declare them as members or even nested functions.

Realize that there are issues with extensions and polymorphism, e.g. this issue I reported 5 years ago: https://youtrack.jetbrains.com/issue/KT-11488

They don’t really pollute the namespace more than member functions since they would only be shown in the same places that members would. You might want to limit their visibility but if you define them correctly this should not be an issue.

Kotlin Arrow uses this heavily. But yeah, apart from functional-oriented libraries, it’s very rare.

1 Like

Here’s the thing in simple terms:

The documentation can talk about advantages and disadvantages if they can do that in a balanced way. But it should not give an unqualified “mostly” like this.

I’ve never seen such a thing in language documentation.

E.g., the Java docs say to use static imports “very sparingly” but that follows a couple of paragraphs of explanation of the reasons.

In this Kotlin document, there is no such explanation.