KITCHEN DRINKER

主にAndroid開発メモとか

Kotlinのスコープ関数を使い分けたい

Kotlin — A deeper look – Hacker Noon より

f:id:nyanyonin:20170820011605p:plain:w300

この図分かり易いと思ってディスプレイに貼ってる。

この図↓も貼ることにした。(2017/10/05追記)

データクラスの話/スコープ関数の話 #rkt - Speaker Deck より

f:id:nyanyonin:20171005103610j:plain:w300

とてもわかりやすい!

この図↓も追加(2018/7/25追記)

Kotlin Demystified: What are 'scope functions' and why are they special? より

f:id:nyanyonin:20180725130318p:plain:w400

以下は自分用の調べたり人から聞いたりしたことのだらだらめも

let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  • 最後の値が戻り値になる
  • 自身がクロージャの引数になる
  • nullableのunwrapによく使う
  • 引数として関数をとる
  • 変換(convert)に向いてる
  • イメージ
    • ◯ func(it)
    • ✖️it.func()

run

public inline fun <T, R> T.run(block: T.() -> R): R = block()
  • クロージャ内の最後の実行結果がrun()の戻り値になる
  • letとwithが合わさったような定義
  • 任意の型Tの拡張関数で、そのTをレシーバとするメソッドのような関数を引数に取る必要がある
  • レシーバーのプロパティとかガンガン使ってなにかする場合に向いてる
  • イメージ
    • ◯ this.func()
    • ✖️func(this)

apply

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  • 返り値は自分自身。なので何か処理を施して自分に返したい時に使う。
  • Fragmentのインスタンスを作って、argumentsをセットする時など、 オブジェクトのインスタンスを生成して、 すぐにいくつかのプロパティを初期化する必要がある場合に向いてる
class RoomActivity : AppCompatActivity() {
  companion object {
    private const val EXTRA_ROOM_ID = "extra_room_id"

    fun createIntent(context: Activity, roomId: Int)
          = Intent(context, RoomActivity::class.java).apply {
      putExtra(EXTRA_ROOM_ID, roomId)
    }
  }

with

public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
  • not 拡張関数
  • 第一引数に任意の型Tを取る。
  • 第二引数に関数を取るが、Tをレシーバとする関数である必要がある
  • applyと似てる
    • with:最後の実行式を返す
    • apply:自身を返す
  • イメージ:英語の意味で考えると分かり易いかも(〜を使って何かをする)

also

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

たろうさんの記事がわかり易かったのでほぼ引用させていただきます。

applyとほぼ同じ。異なる点は「引数が元のレシーバ("hoge")の拡張関数ではなく、元のレシーバを引数に取る関数である」ところ。 その利点は2つ

①名前を付けられるのでコードの可読性が上がる

ラムダ式の内と外でthisの意味が変わらない

// applyを使用
val button = Button(this).apply {
  text = "Click me"
  setOnClickListener { 
    startActivity(Intent(this@MainActivity, NextActivity::class.java))
    // 単なる「this」ではNG   ^
  }
}
// alsoを使用
val button = Button(this).also { button -> 
  button.text = "Click me"
  button.setOnClickListener { 
    startActivity(Intent(this, NextActivity::class.java))
  }
}

同じく「元のレシーバを引数に取る関数」を引数に取るletとの違いは、letは戻り値を自分で決めるのに対し、alsoは元のレシーバが返されるという点

// letを使用
val button = Button(this).let { button -> 
  button.text = "Click me"
  button.setOnClickListener { 
    startActivity(Intent(this, NextActivity::class.java))
  }
  button // letの場合はこれが必要になる
}

めもここまで。

というわけで、やっぱりよくわからん!!!!!\(^o^)/

色んな人に使い分け方聞いてる*1んですが、 人によって微妙に使いどころ(捉え方?)違うような印象だったり・・・

英語の意味を考えて使うようにすると前よりしっくりきた。。ような気がする

まだまだ理解足りてないのでご指摘・補足等ございましたら遠慮なくお願いいたします🙇🙇


参考サイト

Kotlin — A deeper look – Hacker Noon

http://qiita.com/ngsw_taro/items/d29e30809fc8a38691e

Kotlin 1.1で追加された標準ライブラリの関数をざっくり見る - visible true


*1:①返り値を使わない+何か初期化とかするときはwithで、自分自身が欲しい場合はapply、返り値を使いたいときはrun or letみたいな感じ