78

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?

hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 2
    It's probably a matter of opinion, but the classical example is list.sort() instead of Collections.sort(list): static utility functions can become extension functions. – nhaarman Feb 10 '16 at 14:37
  • 2
    @nhaarman interesting point of view on this topic — not every utility function deserves to become an extension: https://medium.com/@dimsuz/extension-functions-are-not-utility-functions-74a5f9b53892 – Ilya Oct 04 '16 at 22:14
  • @Ilya, thanks for the link, the hint seems nice enough to make a mental note. :) – hotkey Oct 04 '16 at 22:47

4 Answers4

85

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.

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 3
    Since you have the most complete answer, here's an additional benefit you could add: Extension functions don't litter the global namespace. If you are inside any code block and invoke Ctrl + Space, all your top-level functions will be listed. Not so with extension functions, which will only be listed for method calls on correctly typed receivers. – Kirill Rakhman Feb 11 '16 at 08:16
10

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.

Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
5

There is at least one case where extension functions are a must - call chaining, also known as "fluent style":

foo.doX().doY().doZ()

Suppose you want to extend the Stream interface from Java 8 with you own operations. Of course, you can use ordinary functions for that, but it will look ugly as hell:

doZ(doY(doX(someStream())))

Clearly, you want to use extension functions for that. Also, you cannot make ordinary functions infix, but you can do it with extension functions:

infix fun <A, B, C> ((A) -> B).`|`(f: (B) -> C): (A) -> C = { a -> f(this(a)) }

@Test
fun pipe() {
    val mul2 = { x: Int -> x * 2 }
    val add1 = { x: Int -> x + 1 }
    assertEquals("7", (mul2 `|` add1 `|` Any::toString)(3))
}
bytefu
  • 114
  • 4
  • 3
    Is this really a general case of "when you want to change an existing API that you cannot control?" – Jayson Minard Feb 10 '16 at 16:50
  • While cool this actually doesn't compile. On 1.0.3 I get:`Error:Kotlin: [Internal Error] java.lang.IllegalStateException: java.io.FileNotFoundException: D:\...\ExampleKt$|$1.class (The filename, directory name, or volume label syntax is incorrect) at org.jetbrains.kotlin.codegen.CompilationErrorHandler$1.reportException(CompilationErrorHandler.java:27) at org.jetbrains.kotlin.codegen.PackageCodegen.generate(PackageCodegen.java:69) ` – David Soroko Jul 08 '16 at 14:52
  • For future readers: there is no problem in the code, the message clearly says that Kotlin compiler cannot find the source file: ```java.io.FileNotFoundException: D:\...\ExampleKt$|$1.class (The filename, directory name, or volume label syntax is incorrect)``` – bytefu Mar 03 '19 at 22:49
  • 1
    Of course, Kotlin has less need of call chaining, due to the availability of scoping functions which can do the same with any functions, whatever they return — e.g. `foo.apply() { doX(); doY(); doZ() }`. – gidds Mar 15 '22 at 09:16
-1

There are cases where you have to use extension methods. E.g. if you have some list implementation MyList<T>, you can write an extension method like

fun Int MyList<Int>.sum() { ... }

It is impossible to write this as a "normal" method.

Landei
  • 54,104
  • 13
  • 100
  • 195
  • 2
    Basically, the question is more about when and why we should prefer `fun MyList.sum()` over `fun sum(list: MyList)`, not really about extension vs member functions. – hotkey Apr 05 '18 at 13:01