Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should one prefer Kotlin extension functions?

In Kotlin, a function with at least one argument can be defined either as a regular non-member function or as an extension function with one argument being a receiver.

As to the scoping, there seems to be no difference: both can be declared inside or outside classes and other functions, and both can or cannot have visibility modifiers equally.

Language reference seems not to recommend using regular functions or extension functions for different situations.

So, my question is: when do extension functions give advantage over regular non-member ones? And when regular ones over extensions?

foo.bar(baz, baq) vs bar(foo, baz, baq).

Is it just a hint of a function semantics (receiver is definitely in focus) or are there cases when using extensions functions makes code much cleaner or opens up opportunities?

like image 427
hotkey Avatar asked Feb 10 '16 14:02

hotkey


People also ask

What is the purpose of Kotlin extension function?

Kotlin extension function provides a facility to "add" methods to class without inheriting a class or using any type of design pattern. The created extension functions are used as a regular function inside that class. The extension function is declared with a prefix receiver type with method name.

What is the benefit of using the extension functions?

Advantages of using Extension FunctionIt can be able to add functionality to a final class. It means the class do not need to be defined as a non-final class. It can be able to add functionality without sub-classing, which means the object reference and the its implementation can be mentioned in your base class.

Where do I put Kotlin extension function?

Actually, it doesn't matter to where you writing them. The extension function are mapped with a class which is already a part of your application. So, it's up to you in which you are going write you extension functions.

What is the difference between a member function and extension function in Kotlin?

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).


2 Answers

Extension functions are useful in a few cases, and mandatory in others:

Idiomatic Cases:

  1. When you want to enhance, extend or change an existing API. An extension function is the idiomatic way to change a class by adding new functionality. You can add extension functions and extension properties. See an example in the Jackson-Kotlin Module for adding methods to the ObjectMapper class simplifying the handling of TypeReference and generics.

  2. Adding null safety to new or existing methods that cannot be called on a null. For example the extension function for String of String?.isNullOrBlank() allows you to use that function even on a null String without having to do your own null check first. The function itself does the check before calling internal functions. See documentation for extensions with Nullable Receiver

Mandatory Cases:

  1. When you want an inline default function for an interface, you must use an extension function to add it to the interface because you cannot do so within the interface declaration (inlined functions must be final which is not currently allowed within an interface). This is useful when you need inline reified functions, for example this code from Injekt

  2. When you want to add for (item in collection) { ... } support to a class that does not currently support that usage. You can add an iterator() extension method that follows the rules described in the for loops documentation -- even the returned iterator-like object can use extensions to satisfy the rules of providing next() and hasNext().

  3. Adding operators to existing classes such as + and * (specialization of #1 but you can't do this in any other way, so is mandatory). See documentation for operator overloading

Optional Cases:

  1. You want to control the scoping of when something is visible to a caller, so you extend the class only in the context in which you will allow the call to be visible. This is optional because you could just allow the extensions to be seen always. see answer in other SO question for scoping extension functions

  2. You have an interface that you want to simplify the required implementation, while still allowing more easy helper functions for the user. You can optionally add default methods for the interface to help, or use extension functions to add the non-expected-to-be-implemented parts of the interface. One allows overriding of the defaults, the other does not (except for precedence of extensions vs. members).

  3. When you want to relate functions to a category of functionality; extension functions use their receiver class as a place from which to find them. Their name space becomes the class (or classes) from which they can be triggered. Whereas top-level functions will be harder to find, and will fill up the global name space in IDE code completion dialogs. You can also fix existing library name space issues. For example, in Java 7 you have the Path class and it is difficult to find the Files.exist(path) method because it is name spaced oddly. The function could be placed directly on Path.exists() instead. (@kirill)

Precedence Rules:

When extending existing classes, keep the precedence rules in mind. They are described in KT-10806 as:

For each implicit receiver on current context we try members, then local extension functions(also parameters which have extension function type), then non-local extensions.

like image 193
Jayson Minard Avatar answered Sep 27 '22 21:09

Jayson Minard


Extension functions play really well with the safe call operator ?.. If you expect that the argument of the function will sometimes be null, instead of early returning, make it the receiver of an extension function.

Ordinary function:

fun nullableSubstring(s: String?, from: Int, to: Int): String? {     if (s == null) {         return null     }      return s.substring(from, to) } 

Extension function:

fun String.extensionSubstring(from: Int, to: Int) = substring(from, to) 

Call site:

fun main(args: Array<String>) {     val s: String? = null      val maybeSubstring = nullableSubstring(s, 0, 1)     val alsoMaybeSubstring = s?.extensionSubstring(0, 1) 

As you can see, both do the same thing, however the extension function is shorter and on the call site, it's immediately clear that the result will be nullable.

like image 24
Kirill Rakhman Avatar answered Sep 27 '22 22:09

Kirill Rakhman