Inline restrictions

Inline lambda functions have an important restriction—they can't be manipulated in any way (stored, copied, and others).

The UserService stores a list of listeners (User) -> Unit:

data class User(val name: String)

class UserService {
val listeners = mutableListOf<(User) -> Unit>()
val users = mutableListOf<User>()

fun addListener(listener: (User) -> Unit) {
listeners += listener
}
}

  Changing addListener into an inline function will produce a compilation error:

inline fun addListener(listener: (User) -> Unit) {
listeners += listener //compilation error: Illegal use of inline-parameter listener
}

If you think about it, it makes sense. When we inline a lambda, we're replacing it for its body, and that isn't something that we can store on a Map.

We can fix this problem with the noinline modifier:

//Warning: Expected performance impact of inlining addListener can be insignificant
inline fun
addListener(noinline listener: (User) -> Unit) {
listeners += listener
}

Using noinline on an inline function will inline just the high-order function body but not the noinline lambda parameters (an inline high-order function can have both: inline and noinline lambdas). The resulting bytecode isn't as fast as a fully inline function, and the compiler will show a warning.

Inline lambda functions can't be used inside another execution context (local object, nested lambda).

In this example, we can't use transform inside the buildUser lambda:

inline fun transformName(transform: (name: String) -> String): List<User> {

val buildUser = { name: String ->
User(transform(name)) //compilation error: Can't inline transform here
}

return users.map { user -> buildUser(user.name) }
}

To fix this problem, we need a crossinline modifier (alternatively, we can use noinline but with the associated performance lost):

inline fun transformName(crossinline transform: (name: String) -> String): List<User> {

val buildUser = { name: String ->
User(transform(name))
}

return users.map { user -> buildUser(user.name) }
}

fun main(args: Array<String>) {
val service = UserService()

service.transformName(String::toLowerCase)
}

The generated code is quite complex. Many pieces are generated:

  • A class that extends (String) -> User to represent buildUser and internally creates User using String::toLowerCase to transform the name
  • A normal inline code to execute List<User>.map() using an instance of the class that represents buildUser
  • List<T>.map() is inline, so that code gets generated too

Once you're aware of its restrictions, inline high-order functions are a great way to increase the execution speed of your code. Indeed, a lot of the high-order functions inside the Kotlin Standard Library are inline.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.93.20