tomoima525's blog

Androidとか技術とかその他気になったことを書いているブログ。世界の秘密はカレーの中にある!サンフランシスコから発信中。

Kotlinの標準ライブラリ(let, apply, with etc)を何となく使うから卒業する

f:id:tomoima525:20170804154058p:plain:w400

Kotlinの標準ライブラリ便利ですよね。Kotlinを書き始めて T.?let{ } の便利さには感動しました。標準ライブラリ色々ありますが、どんなものがあり、どういう挙動をするのかよく把握していなかったので、自分の勉強のために*1まとめてみました。

前提

以下のようなシンプルなクラスを標準ライブラリで触ります。

class Test {
    var message = "test"
    val length: Int get() = message.length
    fun foo(text: String): Unit = println("foo: " + text) 
}

let

定番のやつ。自身を引数としてブロック関数に渡します。変数スコープを限定したいケースで有用ですね。ブロック関数の返り値を設定してない場合は返り値がUnitになります。

// let
// public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
val result = test.let {
  println("let: " + it.length) // 4
  123 // return Unit when nothing is set
}

println("${result::class}") //kotlin.Int
println("result 1: " + result) //123

T?.let { } で Nullじゃない場合に何かする、っていうのは頻出ですね。

with

これも頻出。オブジェクトがもっている関数をスコープ内で呼ぶことができる関数。AndroidだとViewの設定なんかで重宝しますね。これも実は結果が返ります。

// with
// public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
val result2 = with(test) {
    foo("buz")
}

println("result 2: " + resultX) // Unit

run

runはletとwithが合わさったような関数です。内部でオブジェクト名をつけずに関数を呼べて、結果も返ります。同じオブジェクトを使って複数の処理を行いたい場合に綺麗にかけそうですね。

// run
// public inline fun <T, R> T.run(block: T.() -> R): R = block()
val result3 =  test.run {
    println("run: " + length)
    456
}
println("result 3: " + result3) // 456

apply

自分自身をレシーバーとしてブロック関数を実行します。letとはことなりオブジェクト自身が返ります。オブジェクトの値を変更したものを渡したい場合に有効です。オブジェクトそのものが書き換わってしまうのでちょっと注意は必要です。

// apply
// public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

println("result: " + test.message) // test 
val result4 = test.apply {
    message = "applied"
}
println("result 4: " + result4.message) // applied
println("result 5: " + test.message)    // applied

Kotlin 1.1以降に追加されたもの

以下は1.1以降で追加された機能です。

also

自身を引数としてブロック処理を行い、自身を返します。applyのlet版といったところでしょうか。applyでこと足りそうだし、使う用途あまりなさそう?

// also
//public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
val result6 = test.also {
    it.foo("qux")
}

println("result 6: " + result6) // Test@xxx

takeIf

自身を引数とした述語の結果をうけて自身かnullを返します。applayとかとつなげると有用そうですね。

// takeIf
// public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null 
val result7 = test.takeIf{
    it.length == 4  // true
} 

println("result 7: " + result7?.message) // test

val result8 = test.takeIf{
    it.length != 4 // false
} 

println("result 8: " + result8?.message) // null

takeUnless

takeIfの逆バージョン。

// takeUnless
// public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

val result9 = test.takeUnless{
    it.length != 4
}
println("result 9: " + result9?.message) // test

repeat

他のとは異なりただ実行するのみの関数です。指定した回数を引数として与えた関数を繰り返します。

repeat(3, { test.foo(it.toString()) }) //foo: 0, foo: 1, foo: 2

以上です。

参考リンク

http://beust.com/weblog/2015/10/30/exploring-the-kotlin-standard-library/ 上の例の多くは網羅されてます。ただ情報が古くて一部仕様が変わっているのと、Kotlin1.1の内容は記載ないです。

https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/util/Standard.kt 主にここを見ながら Try Kotlinで実際に動かして確認しました。

*1:多分Qiitaにはまとめてあるのだろうけど