daisuzz.log

Kotlinのスコープ関数のユースケースをまとめてみる

Kotlinのスコープ関数について公式ドキュメントを参考に特徴とユースケースをまとめてみます。公式のドキュメントがとてもわかりやすくまとまっているので、詳細はそちらを参考にしてください。Kotlinのversionは1.3.21です。

let

public inline fun <T, R> T.let(block: (T) -> R): R
  • レシーバオブジェクトをラムダの引数(it)として参照する。
  • ラムダの結果を返す

ユースケース

nullチェック

// letを使わない場合
val number: Int? = 1
if(number != null) println(number)

// letを使う場合
val number: Int? = 1
number?.let { println(it) }

メソッドチェーンの結果に対して関数を使いたい場合

// letを使わない場合
val list = listOf("one", "two", "three")
val result = list.map { it.toUpperCase() }.filter { it != "three" }
println(result)

// letを使う場合
val list = listOf("one", "two", "three")
list.map { it.toUpperCase() }.filter { it != "three" }.let { println(it) }

メソッドチェーンの途中結果を読みやすい名前に変換したい場合

// letを使わない場合
val list = listOf("alice", "bob", "chris")
val person = list.first()
println(person.toUpperCase())

// letを使う場合
val list = listOf("alice", "bob", "chris")
val result = list.first().let { person ->
  println(person.toUpperCase())
}

apply

public inline fun <T> T.apply(block: T.() -> Unit): T
  • レシーバオブジェクトをthisとして参照する
  • レシーバオブジェクトを返す

ユースケース

オブジェクトの設定をする場合

// applyを使わない場合
val datasource = SampleDataSource()
datasource.url = ""
datasource.username = ""
datasource.password = ""

// applyを使う場合
val datasource = SampleDataSource().apply{
  // オブジェクトの設定
  url = ""
  username = ""
  password = ""
}

with

public inline fun <T, R> with(receiver: T, block: T.() -> R): R
  • レシーバーオブジェクトthisとして参照する
  • ラムダの結果を返す
  • 拡張関数としてではなく引数としてレシーバーオブジェクトを受け取る

ユースケース

プロパティやメソッドへのアクセスを簡潔にしたい場合

// withを使わない場合
println(datasource.url)
println(datasource.username)
println(datasource.password)

// withを使う場合
with(datasource){
  println(url)
  println(username)
  println(password)
}

run

public inline fun <R> run(block: () -> R): R 
public inline fun <T, R> T.run(block: T.() -> R): R
  • オブジェクトをレシーバーオブジェクトthisとして参照する
  • ラムダの結果を返す

ユースケース

オブジェクトの初期化と、オブジェクトを使った計算を返す処理をまとめて行いたい場合

// runを使わない場合
val datasource = DataSource()
datasource.url = ""
datasource.username = ""
datasource.password = ""
val result = datasource.fetch()

// runを使う場合
val result = DataSource().run{
  // オブジェクトの設定
  url = ""
  username = ""
  password = ""
  // オブジェクトの処理を実行
  fetch()
}

ローカル変数と処理を1つにまとめて名前をつけたい場合

// runを使わない場合
val schema = "https://"
val hostname = "localhost"
val port = ":8080"
val path = "/"
val url = schema + hostname + port + path

// runを使った場合
val url = run{
  val schema = "https://"
  val hostname = "localhost"
  val port = ":8080"
  val path = "/"
  schema + hostname + port + path
}

also

public inline fun <T> T.also(block: (T) -> Unit): T
  • レシーバオブジェクトをラムダの引数(it)として参照する
  • レシーバオブジェクトを返す

ユースケース

オブジェクトのプロパティや関数ではなく、オブジェクトへの参照を必要とする操作を行いたい場合

// alsoを使わない場合
val list = mutableListOf(1, 2, 3)
println(list)
list.add(4)
println(list)

// alsoを使った場合
val list = mutableListOf(1, 2, 3)
list.also { println(it) }
    .add(4)
    .also { println(it) }

外部スコープからのThis参照を暗黙的にしたくない場合

// applyを使う場合
val datasource = SampleDataSource().apply{
  // オブジェクトの設定
  url = ""
  username = ""
  password = ""
}

// alsoを使う場合
val datasource = SampleDataSource().also{
  // オブジェクトの設定
  it.url = ""
  it.username = ""
  it.password = ""
}

最後に

スコープ関数のユースケースについて公式ドキュメントを参考にまとめてみました。 公式ドキュメントにも書いてある通り、スコープ関数をネストさせたり、むやみにスコープ関数をメソッドチェーンしすぎると、itやthisが乱立してしまい、読みづらいコードになってしまうので注意が必要です。

参考資料

https://kotlinlang.org/docs/reference/scope-functions.html#scope-functions