Kotlinのスコープ関数の定義を読み解く
※本記事はQiitaに投稿していた記事のexportです(元投稿日: 2018/8/13)
Kotlinのスコープ関数はlet, with, run, apply, alsoがあります。
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
などのそれぞれの定義を読んでもすぐ頭に入ってこないので、読み方を解説してみます。自分用チートシートです。
前提知識
拡張関数
- クラスを継承したりDecoratorのようなデザインパターンを使用せずとも、クラスを新しい機能で拡張できる言語機能
- 公式 https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/extensions.html
- Kotlin イン・アクション P.68
高階関数
- 引数として関数を取るか、関数を返り値とする関数
- 公式 https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/lambdas.html
- Kotlin イン・アクション P.264〜
インライン関数
- inline修飾子をつけると、関数呼び出しを生成せず、実装する実際のコードに置換する機能(スコープ関数の定義に使われているが、使うときには特に意識しなくても良い)
- 公式 https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/inline-functions.html
- Kotlin イン・アクション P.277
ジェネリクス
- 型パラメータ(Kotlinではよく
<T>
のように表される)を持った型を定義できる仕組み - 公式 https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/generics.html
- Kotlin イン・アクション P.294
let
定義
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Calls the specified function [block] with
this
value as its argument and returns its result.
- 自分自身Tを引数としてblock関数を実行し、その結果を返します。
- Kotlin イン・アクション P.189
使用例
fun sendEmail(email: String): Boolean { /* ... */ } val result = email?.let { sendEmail(it) } // it = email
安全呼び出し演算子と使えば、emailがnullでないときだけ、emailを引数にとってsendEmailを実行する、といった処理を完結に書けます。letを使わない場合はこのようにnullチェックが必要になり冗長です。
var result = false if (email != null) { result = sendEmail(email) }
英語としてわかりやすい例
- 英語の文法的には、「let + <目的語> + <動詞>」となり、「<目的語>に<動詞>をさせる」という意味です。
val s = "hoge".let { it.toUpperCase() } // s = "HOGE"
ただこの例だと後述のrunを使った方が "hoge".run { toUpperCase() }
とスッキリします。対象オブジェクトを引数にしたいときに使う方が良さそうです。
使い時
if (hoge != null) ...
のような記述をしたくなったとき(これが圧倒的に多い)- 対象オブジェクトを引数にして、まとめて処理を行いたいとき
- thisを変えたくないとき(関数内でcontextを使った処理があるなど。this@MainActivityのような指定も可能ではある)
// Activity内で val s = "hoge".let { Toast.makeText(this, it, Toast.LENGTH_LONG).show(); it.toUpperCase() }
with
定義
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
- 第一引数に任意の型Tを取り、そのTをレシーバとして第二引数を実行します。
- 同じオブジェクトに対してその名前を繰り返すことなく複数の操作を出来る関数です。
- Kotlin イン・アクション P.169
使用例
val numbers = with(StringBuilder()) { append("Numbers: ") for (i in 1..3) append(i) toString() // 全ての行でthisを省略出来て読みやすい } println(numbers) // Numbers: 123
英語としてわかりやすい例
- withは前置詞で、後ろに名詞(句・節)か動名詞が来ます。引数として渡すので名詞が良いでしょう。〜とともに、〜を使って、などの意味があります。
val love = with(you){ date(me) } // letを使って同じ処理を書く場合 val love = you.let { it.date(me) }
letを使っても同じ書き方は出来ますが、英語の通りとしてはwithを使った方が読みやすくなります。
使い時
- 同じオブジェクトに対して繰り返し操作したいとき
- 返り値が必要ないときは apply と同じで、どちらでも使える
- あまり使われてはいない模様
run
定義
public inline fun <T, R> T.run(block: T.() -> R): R = block()
Calls the specified function [block] with
this
value as its receiver and returns its result.
- 自分自身Tをレシーバとする関数を実行し、その結果を返します。
- Kotlin イン・アクションには出てきませんでした。
使用例
val numbers = StringBuilder().run { append("Numbers: ") for (i in 1..3) append(i) toString() } println(numbers) // Numbers: 123
- withの例はrunでも同じように書けますね。
英語としてわかりやすい例
- 使いどころが限定されていてあまり浮かびません…
使い時
apply
定義
public inline fun <T> T.apply(block: T.() -> Unit): T = return this
Calls the specified function [block] with
this
value as its receiver and returnsthis
value.
- 自分自身Tをレシーバとする関数を実行し、実行後の自分自身を返します。
- Kotlin イン・アクション P.171
使用例
withと似た動きをします。異なるのは、applyを実行した返り値はthisなのでStringBuilderであるため、Stringに変換するtoString()が必要な点です。
val numbers = StringBuilder().apply { append("Numbers: ") for (i in 1..3) append(i) }.toString() println(numbers) // Numbers: 123
英語としてわかりやすい例
- applyは適用する・適用されるという意味です。設定画面に出てくるapplyのイメージで良さそう。
// Activityで supportActionBar?.apply { setHomeButtonEnabled(true) setDisplayHomeAsUpEnabled(true) }
使い時
- ローカル変数を書かずにオブジェクトの状態を変更したいとき。上記「英語としてわかりやすい例」がそれです。
- オブジェクトのインスタンスを生成し、すぐにいくつかのプロパティを初期化する必要があるとき。
val intent = Intent(this, FooActivity::class.java).apply { putExtra("id", user.id) putExtra("name", user.name) }
also
定義
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
Calls the specified function [block] with
this
value as its argument and returnsthis
value.
- 自分自身Tを引数とする関数を実行し、実行後の自分自身を返します。
- Kotlin イン・アクションには出てきませんでした。
使用例
val button = Button(context).also { it.text = "hello" }
applyの方がわかりやすくスッキリするようにも見えるのですが、Kotlinアンチパターンで注意事項が書かれていました。
// 最初にこういったコードの内は大丈夫なんですが… val button = Button(context).apply { text = "hello" }
val text = "" // 後でこのコードが追加されると val button = Button(context).apply { text = "hello" } button.text // "hello"にならない
ということで、applyを禁止してalsoとする場合もあるようです。
使い時
- applyと同じようにまとめて設定を行いたいときで、thisを変えたくないとき
自分の使用状況
let と apply をよく使っています。逆に他はほとんど使っていません。 Kotlinアンチパターンの例のように、わかりやすさのために使うスコープ関数の種類を絞るのも有りだと思いました。