Extension vs. member functions


#1

The style guide says “In particular, when defining extension functions for a class which are relevant for all clients of this class, put them in the same file where the class itself is defined.”.
Why would I not make member functions instead then? Is there an advantage when using extension functions?


#2

The main difference between member method versus extension function is that the first uses runtime dispatch (meaning dispatch based on runtime type of the object), when the second one uses compile-time dispatch (based on compile-time type information). The second one is much more reliable and allows more flexibility in the code. For example you can dispatch based on generic type (you can’t do it with members because type erasure).

Even if you do not look into the style guide, you will soon start to replace all non-essential methods with extensions. The only problem with extensions is that they look really ugly from the java code.


#3

For example you can also define your extension function on the nullable type, making it Null-Safe.

fun String?.isNullOrEmpty() {
    this == null || this == ""
}

This is not possible with a member function.


#4

Ah, nice, thanks for the answers.


#5

I have another related question, maybe you two can also help on this.
In the style guide it also says

and do not separate regular methods from extension methods.

Maybe it is just my lack of knowledge about the syntax, but aren’t extension methods defined outside the class declaration? How can you then mix them with regular methods?


#6

You can have top level regular functions as well as class member extension functions. I understand it that you should not order these 2 kind of functions into groups.


#7

By the way, I do not know what style guide says, but you should completely avoid top level functions if possible. You could use them in small or endpoint application, but in library they bloat global namespace and produce name conflicts.


#8

You need to be more specific. Private top-level functions are perfectly fine, as well as public extension functions on top level. For regular functions, I agree.


#9

I agree about private ones. Even internals are OK.


#10

Ah, I see, I thought about how to not group the functions in

class Test {
   fun foo() {}

   fun bar() {}
}

fun Test.baz() {}

fun Test.bam() {}

but with this it makes sense of course:

class Test {
   fun foo() {}

   fun bar() {}
}

fun Test.baz() {}

fun boo() {}

fun Test.bam() {}

#11

The namespace concern doesn’t exist if you’re using packages for namespacing, which is what they’re for. Also, every top-level function name must be explicitly imported, unless you use * imports. Even if you want to import two functions with the same name, you can resolve the conflict using an alias import.


#12

Whenever you dereference this from an extension function, you get dynamic dispatch as well.

The only difference is that there’s no dynamic dispatch to choose the implementation of the extension function. So, if you have fun MainType.callme() and fun SubType.callme(), the compiler decides ahead of time which one will be called. But this is not an advantage of the mechanism.

With respect to generics, they are on an equal footing with member functions, but I guess there are some specific scenarios where you benefit from the ahead-of-time function selection.


#13

It really does not help with IDE autocomplete. Also functions with the same name in different packages bring a lot of confusion.


#14

When people are talking about dispatch, they usually mean function call resolution. In this sense the dispatch for extensions is static. Of course you can always use reflections to access specific instance of ‘this’, but reflects are always dynamic.


#15

Top-level functions are bog-standard in most other languages, though. It kind of seems to work out, there aren’t many clashes and reading such code feels very natural.

If the alternative, using a type name for namespacing and qualifying each and every function call, didn’t have many well-known downsides, we wouldn’t be discussing it here.

Neither approach is a clear-cut winner, there are particular cases where qualifying all calls to some functions does make sense. There are also particular cases where not qualifying the calls is by far preferred.

In essence, I don’t find a short quip on the subject to be good advice in the long term. Learn both, use and even overuse both, see where each approach is a better fit and where it’s mostly an obstacle.


#16

Could you please provide an example of language and library which provides a specialized function in the global scope and it benefits someone? Even numpy math functions like sin are always used with np qualifier.


#17

Now you already narrowed it to a “specialized function” which begs the question of what would qualify. Clearly you must qualify an alternative version of sin. Also, you say “the global scope”, but I never argued for adding anything to the global scope (unless you consider all namespaces as “global scope”).

In each file you can choose what functions you make available without qualification. On the example of Java, which is a language of massive codebases, you can use the static import quite a lot before running into any issues.