daisuzz.log

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) }

最後に

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

参考資料

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