# Effective Kotlin Item 46: Avoid member extensions

This is a chapter from the book Effective Kotlin. You can find it on LeanPub or Amazon.

When we define an extension function to some class, it is not added to this class as a member. An extension function is just a different kind of function that we call on the first argument that is there, called a receiver. Under the hood, extension functions are compiled to normal functions, and the receiver is placed as the first parameter. For instance, the following function:

fun String.isPhoneNumber(): Boolean = length == 7 && all { it.isDigit() }

Under the hood is compiled to a function similar to this one:

fun isPhoneNumber($this$: String): Boolean = $this$.length == 7 && \$this.all { it.isDigit() }

One of the consequences of how they are implemented is that we can have member extensions or even define extensions in interfaces:

interface PhoneBook { fun String.isPhoneNumber(): Boolean } class Fizz : PhoneBook { override fun String.isPhoneNumber(): Boolean = this.length == 7 && this.all { it.isDigit() } }

Even though it is possible, there are good reasons to avoid defining member extensions (except for DSLs). Especially, do not define extension as members just to restrict visibility.

// Bad practice, do not do this class PhoneBookIncorrect { fun verify(number: String): Boolean { require(number.isPhoneNumber()) // ... } // ... fun String.isPhoneNumber(): Boolean = this.length == 7 && this.all { it.isDigit() } }

One big reason is that it does not really restrict visibility. It only makes it more complicated to use the extension function, since the user would need to provide both the extension and dispatch receivers:

PhoneBookIncorrect().apply { "1234567890".isPhoneNumber() }

You should restrict an extension visibility using a visibility modifier, not by making it a member.

class PhoneBook { fun verify(number: String): Boolean { require(number.isPhoneNumber()) // ... } // ... } // This is how we limit extension functions visibility private fun String.isPhoneNumber(): Boolean = this.length == 7 && this.all { it.isDigit() }

If you need a function to be a member, for instance when it needs to use class state, and you want to call it like an extension, consider using let.

class PhoneBook( private val phoneNumberVerifier: PhoneNumberVerifier ) { fun verify(number: String): Boolean { require(number.let(::isPhoneNumber)) // ... } // ... private fun isPhoneNumber(number: String): Boolean = phoneNumberVerifier.verify(number) }

### Why avoiding extension functions?

There are a few good reasons why we prefer to avoid member extensions:

• Reference is not supported:
val ref = String::isPhoneNumber val str = "1234567890" val boundedRef = str::isPhoneNumber val refX = PhoneBookIncorrect::isPhoneNumber // ERROR val book = PhoneBookIncorrect() val boundedRefX = book::isPhoneNumber // ERROR