What is it, naokirin?

Kotlinのスコープ関数について今さらまとめておく

久々にKotlinを書き始めたものの、1年以上間が空いているので、ざっと言語仕様と標準ライブラリについてを学び直し中です。

そこで、Kotlinの特徴的な機能の一つである「スコープ関数」をまとめておくことにしました。

スコープ関数とは

Kotlinにおけるスコープ関数というのは、「オブジェクトのコンテキストで実行することを目的とした関数」とされています。それをなぜスコープ関数と呼ぶかというと、以下のような機能を備えているからです。

  • スコープ関数は、ラムダ式を受け取る高階関数
  • 受け取ったラムダ式内で、オブジェクトを名前無しで呼び出し可能な一時スコープが生成される

Kotlinの標準関数には、 letrunwithapplyalso の5つのスコープ関数が存在します。

スコープ関数の種類

各スコープ関数の説明の前に、大きく2つの種類に分類されるので、それについて説明しておきます。

this と it

runwithapplythis をラムダのレシーバーとして使用できるようになります。 this を利用できるので、レシーバの変数名を省略して呼び出すことができるようになります。

val list = mutableListOf(1, 2, 3).apply {
    add(4)
    removeIf { it % 2 != 0 }
}
println(list)

// output:
//    [2, 4]

一方で、letalsoit で参照できるようにします。 it で参照できるだけであれば、そのまま変数を参照すれば良いと思うかもしれませんが以下のようなパターンでは冗長な変数代入や呼び出しをせずに済むようにできます。

fun greeting(name: String?): String {
    return name?.let { "Hello, $it!" } ?: "Hello!"
}

println(greeting("Bob"))

// output:
//    Hellow, Bob!

戻り値

applyalso はコンテキストオブジェクトを返します。

val list = mutableListOf(1, 2, 3).apply {
    add(4)
    println("added 4.")
}
println(list)

// output:
//   added 4.
//   [1, 2, 3, 4]

一方で、 letrunwith はラムダの結果を返します。

val result = mutableListOf(1, 2, 3).let {
    it.add(4)
    "result: $it"
}
println(result)

// output:
//   result: [1, 2, 3, 4]

apply

apply はオブジェクトに設定をしていくための関数と考えることができます。

class User {
    var name: String = ""
    var age: Int = 0
}

val user = User().apply {
    name = "Alice"
    age = 15
}
println("I am ${user.name}, ${user.age} years old.")

// output:
//   I am Alice, 15 years old.

let

let は活用することで、冗長な変数呼び出しの繰り返しなどを避けることができます。

val result = listOf(2, 3).first().let { it * it }
println(result)

// output:
//    4

run

run は、引数でオブジェクトを受け取るような処理を連鎖させたい場合、メソッドチェーンのようにして記述できます。

fun isInteger(str: String): Boolean = str.toIntOrNull() != null
fun validAgeMessage(isInteger: Boolean): String = if (isInteger) "valid." else "not valid."

"10".run(::isInteger).run(::validAgeMessage).run(::println)

// output:
//   valid.

with

withrun と同じ振る舞いをしますが、呼び出す際に第一引数に渡して実行します。一般的には、 run が好まれており、基本的には run を使うようにするのが推奨されているようです。

val result = with("1, 2, 3") { split(",") }
println(result)

// output:
//   [1, 2, 3]

also

alsolet と同じような挙動をしますが、レシーバを返す点が異なります。そのため、副作用を実行したい場合に有用です。

val result = mutableListOf(1, 2, 3).also { println(it.size) }
                                                         .apply { add(4) }
                                                         .also { println(it.size) }
println(result)

// output:
//   3
//   4
//   [1, 2, 3, 4]

まとめ

ざっとKotlinのスコープ関数について記載しました。

スコープ関数を用いることで、シンプルな記述を目指すことができるので、ぜひともマスターしたいところです。