Functions

Function Literal with Receiver


inline fun createString(block: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.block()
    return sb.toString()
}

Since the receiver is defined as StringBuilder, a client will be able to pass lambdas to createString that make use of that receiver. The receiver is exposed as this inside the lambda, which means that clients can access visible members (properties, functions etc.) without additional qualifiers:


val s = createString { //here we're in the context of a `StringBuilder`
    append(4)
    append("hello")
}
println(s)

//Output
4hello

Scope Functions

They are defined as higher-order functions, i.e. they take another function as their argument. These arguments may even appear as function literals with receiver in certain cases. Scope functions take an arbitrary object, the context object, and bring it to another scope. In that scope, the context object is either accessible as it (or custom name) or this, depending on the type of function.

let


public inline fun <T, R> T.let(block: (T) -> R): R

a. Idiomatic replacement for if (object != null) blocks

It is supposed to be used to execute blocks if a certain object is not null.


val len = text?.let {
    println("get length of $it")
    it.length
} ?: 0

b. Map nullable value if not null


val mapped = value?.let { transform(it) } ?: defaultValue

c. Confine scope of variable/computation

run


inline fun <T, R> T.run(block: T.() -> R): R

The run function is like let except how block is defined.

also

also looks like let, except that it returns the receiver T as its result.

apply


val bar: Bar = Bar().apply {
    foo1 = Color.RED
    foo2 = "Foo"
}
println(bar)

Although also was already shown as a tool for solving these scenarios, it’s obvious that apply has a big advantage: There’s no need to use “it” as a qualifier since the context object, the Bar instance in this case, is exposed as this.

Builder-style usage of methods that return Unit


data class FooBar(var a: Int = 0, var b: String? = null) {
    fun first(aArg: Int): FooBar = apply { a = aArg }
    fun second(bArg: String): FooBar = apply { b = bArg }
}

fun main(args: Array<String>) {
    val bar = FooBar().first(10).second("foobarValue")
    println(bar)
}

In the example, apply is used to wrap simple property assignments that would usually simply result in Unit.

with

//return arbitrary value R, not an extension function, T exposed as `this`
fun <T, R> with(receiver: T, block: T.() -> R): R

Working with an object in a confined scope


val s: String = with(StringBuilder("init")) {
    append("some").append("thing")
    println("current value: $this")
    toString()
}
println(s)

Comparison and Overview


//return receiver T
fun T.also(block: (T) -> Unit): T //T exposed as it
fun T.apply(block: T.() -> Unit): T //T exposed as this

//return arbitrary value R
fun <T, R> T.let(block: (T) -> R): R //T exposed as it
fun <T, R> T.run(block: T.() -> R): R //T exposed as this

//return arbitrary value R, not an extension function
fun <T, R> with(receiver: T, block: T.() -> R): R //T exposed as this

Apply vs With

apply accepts an instance as the receiver while with requires an instance to be passed as an argument. In both cases the instance will become this within a block.

apply returns the receiver and with returns a result of the last expression within its block.

The apply function

//returns receiver T, T exposed as `this`
fun <T> T.apply(block: T.() -> Unit): T

The apply function is invoked on a receiver T, which will be exposed as *this` in the passed lambda expression. The receiver also becomes the result of apply automatically.

The with function

//return arbitrary value R, not an extension function, T exposed as `this`
fun <T, R> with(receiver: T, block: T.() -> R): R

The with function, as opposed to all other scope functions (let, run, also, apply), is not defined as an extension function. Instead, the function is invoked with a receiver object as its first argument explicitly. Same as apply, the receiver is exposed as this in the passed lambda. The result of the lambda, i.e. it’s last statement, becomes the result (R) of with.

results matching ""

    No results matching ""