9
9

More than 5 years have passed since last update.

Kotlin標準関数の習得 run, with, let, also and applyの日本語訳

Posted at

自分の学習のため日本語訳しながら理解していく。

元記事はこちら

Mastering Kotlin standard functions: run, with, let, also and apply
https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84

Kotlinの標準的な機能の習得

Kotlinの標準関数のいくつかはとても似ているので、どちらを使うべきかわからない。それらの違いを明確に区別するための簡単な方法と、どちらを使用するかを選ぶ方法を紹介します。

スコープ関数

私が注目する機能は、T.runT.letT.alsoT.apply
それらをスコープ関数と呼ぶ、

スコープを説明する最も簡単な方法はrun関数です。

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}}

test関数の中でprintln前にmoodが再定義され別のスコープを持つことになる。

run {
    if (firstTimeView) introview else normalView
}.show()

スコープ関数の3つの属性

1.通常関数と拡張関数

withT.runを見てみると、両者の関数はかなり似ている。

with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}

webview.settings.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

しかしそれらの違いは、withが通常の関数、T.runが拡張関数である。
それぞれの問題/利点は何だろう?

webview.settingsがnullになる可能性があると想像してみよう。

// イケてない
with(webview.settings) {
    this?.javaScriptEnabled = true
    this?.databaseEnabled = true
}

// ナイス
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

この場合は、runを使用する前にnull許容のチェックができるため、明らかにrunの拡張関数が優れている。

This 対 it 引数

runletを見ると、2つの関数は1つの点を除いて似ているが、引数を受け入れる方法が異なる。
以下は両方の機能に対する同じロジックを示している。

// runの場合
stringVariable?.run {
    println("この文字列の長さは$length")
}

// letの場合
stringVariable?.let {
    println("この文字列の長さは${it.length}")
}

T.run関数のシグネチャを確認すると、それがT.run拡張関数呼び出しとして作成されたものであることがわかりblock: T.()ます。したがって、範囲内であればすべてT、this.Inプログラミングと呼ぶthisことができ、ほとんどの場合省略することができます。したがって、上記の例$lengthでは、printlnステートメントの代わりにを使用できます${this.length}。これを引数として送ることとしてこれを呼びます。

しかしT.let関数シグネチャの場合はT.let、関数に自分自身を送信していることに気付くでしょうblock: (T)。それゆえ、これはラムダ引数がそれを送ったようなものです。それはスコープ関数の中では参照できますit。それでこれを引数として送ることと呼びます。

上記から、暗黙的であることT.runよりも優れているように思われますが、以下のようT.letにT.let機能のいくつかの微妙な利点があります。

  • T.let明確に区別使用して、外部のクラス関数/メンバ対与えられた変数機能/部材を提供し
  • this省略することができない場合、例えばitが関数のパラメータとして送られるとき、書くよりも短く、そしてthisより明確になります。
  • T.letは変換後の使用済み変数の命名が容易になります。つまり、itを他の名前に変換することができます。
stringVariable?.let {
    nonNullString ->
    println("The non null string is $nonNullString$")
}

3. 自分を返す vs 他のタイプを返す

それでは、T.letとT.also見てみましょう。内部の機能範囲を調べれば、どちらも同じです。

stringVariable?.let {
    println("The length of this String is ${it.length}")
}

// 全く同じ
stringVariable?.also {
    println("The length of this String is ${it.length}")
}

しかし彼らの微妙な違いは彼らが返すものです。 T.letは異なるタイプの値を返し、T.alsoはT自体、つまりこれを返します。

どちらも機能を連鎖させるのに役立ちます。
T.letはあなたが操作を進化させることを可能にし、T.alsoもあなたが同じ変数、T.alsoも同じ変数、つまりthisで実行させます。

val original = "abc"
// 値を進化させて次のチェーンに送る
original.let {
    println("元の文字列は ${it}") // "abc"
    it.reversed() // itを次のletに送るためのパラメータとして発展させる
}.let {
    println("逆の文字列は ${it}") // "cba"
    it.length // 他のタイプに発展させることができる
}.let {
    println("文字列の長さは ${it}")
}

// 間違い
// 同じ値がチェーンで送信されます(printlnされた答えが間違っています)
original.also {
    println("元の文字列は ${it}") // "abc"
    it.reversed() // itを進化しても使われない
}.also {
    println("逆の文字列は ${it}") // "abc"
    it.length  // itを進化しても使われない
}.also {
    println("文字列の長さは ${it}") // "abc"
}


// 元の文字列として操作する
// 同じ値がチェーンで送信されます
original.also {
    println("元の文字列は ${it}") // "abc"
}.also {
    println("逆の文字列は ${it.reversed()}") // "cba"
}.also {
    println("文字列の長さは ${it.length}") // 3
}

T.alsoは、これらを単一の機能ブロックに簡単に組み合わせることができるので、上記では意味がないように思われるかもしれません。慎重に考えて、それはいくつかの良い利点があります

  1. 同じ対象物に対して非常に明確な分離プロセスを提供することができ、すなわちより小さな機能部分を作ることができる。
  2. 使われる前に自己操作のために非常に強力になることができて、連鎖ビルダー操作を作ります。

両方が鎖を結合するとき、すなわち、一方がそれ自身を進化させ、一方がそれ自身を保持するとき、それは強力な何かになる。

// 通常アプローチ
fun makeDir(path: String): File {
    val result = File(path)
    result.mkdirs()
    return result
}

// 改善後アプローチ
fun makeDir(path: String) = path.let { File(it) }.also {it.mkdires() }

すべての属性を見る

3つの属性を見れば、関数の動作をほとんど知ることができました。 T.apply関数については、上記では説明していないので、ここで説明します。

T.apply

T.applyの3つの属性は以下の通りです

  1. 拡張関数である
  2. thisを引数とする
  3. thisを返す

それゆえ、それを使うと、想像することができ、それは、

// ノーマルアプローチ
fun createInstance(args: Bundle) :MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}

// 改善アプローチ
fun createInstance(args: Bundle) = MyFragment().apply { arguments = args }

あるいは、連鎖のないオブジェクト作成を連鎖可能にすることもできます。

// ノーマルアプローチ
fun createIntent(intentData: String, intentAction:String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data = Uri.parse(intentData)
    return intent
}

// 改善アプローチ
fun createIntent(intentData: String, intentAction: String) {
    Intent.apply { action = intentAction }
          .apply { data = Uri.parse(intentData) }
}
9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9