スコープ関数はKotlinの標準ライブラリで提供されています。
そのことから、Kotlinを特徴付ける重要な構文であることが分かります。
ただし、「プログラムの動作を定義する構文」ではなく、「プログラムのコードの品質を上げる構文」です。
同様な動作はスコープ関数を使わなくても記述できます。しかし、スコープ関数を使えばスマートな(洗練された)記述になります。
積極的に使いたいと思いますが、applyとlet以外はあまり使う機会がありません。また、稀にwithなどが登場すると、コードの流れが理解できずに戸惑います。
ですので、スコープ関数について、まとめました。
※環境:Android Studio Hedgehog | 2023.1.1 Patch 2
Kotlin 1.9.0
Compose Compiler 1.5.1
スコープ関数とは
スコープ関数は、オブジェクト(レシーバー)が参照可能な範囲(スコープ)で、コードブロック(ラムダ式)を実行する関数です。

「apply、run、also、with、let」の5つがあります。各々の特徴は表の通りです。
| スコープ関数 | レシーバー(T) | 戻り値(R) | |
|---|---|---|---|
| T.apply | this | レシーバー | |
| T.run | (タイプ2) | ラムダ式の結果 | |
| T.also | it | レシーバー | |
| with(T) | ラムダ式の結果 | ||
| T.let | |||
| run | (タイプ1) | なし | |
| ※ラムダ式の戻り値は{ }内で最後に評価した式の値 | |||
使用頻度が高いのは「apply」と「let」です。
※「Kotlinの標準ライブラリ Scope functions」も合わせて参照して下さい。
apply
オブジェクトへ一連の設定を行った後に構築を行うようなBuilderパターン(デザインパターンの一種)を採用したクラスで利用されます。
fun Greeting_Apply(name: String): String {
val _builder = StringBuilder(name).apply {
insert(0, "[[ Hello ") // 設定1
append(" !! ]]") // 設定2
}
return _builder.toString() // 構築
}
apply関数は拡張関数です。レシーバーはコードブロック内でthisにより参照できます。
戻り値はレシーバーです。
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
alsoとの違いはレシーバーの参照方法のみです。また、run(タイプ2)との違いは戻り値のみです。
also
用途はapplyと同じです。
fun Greeting_Also(name: String): String {
val _builder = StringBuilder(name).also {
it.insert(0, "[[ Hello ") // 設定1
it.append(" !! ]]") // 設定2
}
return _builder.toString() // 構築
}
also関数は拡張関数です。しかし、レシーバーはラムダ式の引数で与えられるため、コードブロック内でitにより参照できます。
戻り値はレシーバーです。
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
applyとの違いはレシーバーの参照方法のみです。
with
オブジェクトとそれを使った処理の関係を強調したい場合に利用されます。関係を強調すると、一括りにグループ化した印象を持たせることが出来ます。
fun Greeting_With(name: String): String {
return with(StringBuilder(name)) {
insert(0, "[[ Hello ")
append(" !! ]]")
toString()
}
}
with関数は一般的な関数です。レシーバーは関数の引数で与えられますが、コードブロックがレシーバ付きラムダ式なので、コードブロック内でthisにより参照できます。
戻り値はラムダ式の結果です。
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
let
オブジェクトのnon-nullable判定の分岐で利用されます。
fun Greeting_Let(name: String?): String {
return name?.let { // name ≠ nullの時
val _builder = StringBuilder(name)
.insert(0, "[[ Hello ")
.append(" !! ]]")
_builder.toString()
}?: "[[ Hello Mr.X !! ]]" // name = nullの時
}
fun Greeting_noLet(name: String?): String {
return if(name != null) {
val _builder = StringBuilder(name)
.insert(0, "[[ Hello ")
.append(" !! ]]")
_builder.toString()
} else "[[ Hello Mr.X !! ]]"
}
let関数は拡張関数です。しかし、レシーバーはラムダ式の引数で与えられるため、コードブロック内でitにより参照できます。
戻り値はラムダ式の結果です。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
run(タイプ1)
処理をローカル化したい時に利用されます。ローカル化された処理は各々が独立しています。ですので、変数はその内部でのみ有効です。
fun Greeting_Run1(name: String): Pair<String, String> {
val _value1 = run { // ローカルブロック1
val _builder = StringBuilder(name) // 同じ名前(_builder)が利用可能
.insert(0, "[[ Hello ")
.append(" !! ]]")
_builder.toString()
}
val _value2 = run { // ローカルブロック2
val _builder = StringBuilder(name) // 同じ名前(_builder)が利用可能
.insert(0, "[[ Hi !! ")
.append(" !! ]]")
_builder.toString()
}
return Pair(_value1, _value2)
}
run(タイプ1)は一般的な関数です。レシーバ―は持ちません。コードブロックの実行のみを行います。
戻り値はラムダ式の結果です。
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
run(タイプ2)
用途はapplyと同じです。
fun Greeting_Run2(name: String): String {
return StringBuilder(name).run {
insert(0, "[[ Hello ")
append(" !! ]]")
toString()
}
}
run(タイプ2)関数は拡張関数です。レシーバーはコードブロック内でthisにより参照できます。
戻り値はラムダ式の結果です。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
applyとの違いは戻り値のみです。
Appendix(サンプルの実行結果)
System.out.println(Greeting_Apply("Taro"))
System.out.println(Greeting_Run1("Jiro").first)
System.out.println(Greeting_Run1("Jiro").second)
System.out.println(Greeting_Run2("Tama"))
System.out.println(Greeting_Also("Yuri"))
System.out.println(Greeting_With("Mako"))
System.out.println(Greeting_Let("Poci"))
System.out.println(Greeting_Let(null))
System.out.println(Greeting_noLet("Hati"))
System.out.println(Greeting_noLet(null))
[[ Hello Taro !! ]] [[ Hello Jiro !! ]] [[ Hi !! Jiro !! ]] [[ Hello Tama !! ]] [[ Hello Yuri !! ]] [[ Hello Mako !! ]] [[ Hello Poci !! ]] [[ Hello Mr.X !! ]] [[ Hello Hati !! ]] [[ Hello Mr.X !! ]]
関連記事:
