[Kotlin]スコフ関数

36365 ワード


目的


  • Executing a lambda on non-null objects: let

  • Introducing an expression as a variable in local scope: let

  • Object configuration: apply

  • Object configuration and computing the result: run

  • Running statements where an expression is required: non-extension run

  • Additional effects: also

  • Grouping function calls on an object: with
  • cottlin scope関数の唯一の目的は、オブジェクトのコンテキストでコードを実行することです.
    これらの関数がラムダ式のオブジェクトに対して呼び出されると、一時的なスキャンが形成される.名前なしでオブジェクト(オブジェクトを表す)にアクセスできます.この関数はscope関数と呼ばれ、5種類あります.
    let, run, with, apply, also
    デフォルトでは、関数は同じ役割を果たします.
    :オブジェクト上でコードブロックを実行する
    異なる点は、このオブジェクトのブロック内の可用性と式全体の結果です.
    
    Person("Alice", 20, "Amsterdam").let {
        println(it)
        it.moveTo("London")
        it.incrementAge()
        println(it)
    }
    Person(name=Alice, age=20, city=Amsterdam)
    Person(name=Alice, age=21, city=London)
    letを使用しない場合は、新しい変数の作成と使用時に名前を繰り返す必要があります.
    val alice = Person("Alice", 20, "Amsterdam")
    println(alice)
    alice.moveTo("London")
    alice.incrementAge()
    println(alice)
    Person(name=Alice, age=20, city=Amsterdam)
    Person(name=Alice, age=21, city=London)
    scope関数の類似特性のため,それらの間の主な違いを理解することが重要である.
    主な違いは2つあります.
  • コンテキストオブジェクト
  • を参照
  • 戻り値
  • Context Object: this or it


    ramdascope関数では、contexオブジェクトは実際の名前ではなく短い参照を使用できます.各scope関数は、contextオブジェクトにアクセスする2つの方法の1つを使用します.
    this:ramda受信者
    it:ランダ変数
    同じ機能を提供します.ここでは、それぞれのメリットとデメリットを紹介し、使い方をお勧めします.
    fun main() {
        val str = "Hello"
        // this
        str.run {
            println("The string's length: $length")
            //println("The string's length: ${this.length}") // does the same
        }
    
        // it
        str.let {
            println("The string's length is ${it.length}")
        }
    }
    The string's length: 5
    The string's length is 5

    this


    run、with、applyは、コンテキストthisキーワードからlamda受信者として参照されます.
    したがって、オブジェクトは通常のクラス関数で使用できます.
    ほとんどの場合、このアイテムは、受信オブジェクトのメンバーにアクセスすると削除できます.しかし、これは、受信オブジェクトと外部オブジェクトまたは関数を区別するのは難しい.したがって、context objectを受信オブジェクトとするthisは、計算オブジェクトメンバーのram式(オブジェクトの関数を呼び出すか、プロセスに値を割り当てる)に推奨されます.
    val adam = Person("Adam").apply { 
        age = 20                       // same as this.age = 20 or adam.age = 20
        city = "London"
    }
    println(adam)
    Person(name=Adam, age=20, city=London)
        data class Apple(var weight: Int)
            class AppleTree(val appleTree: List<Apple>) {
                fun pick(): Apple {
                    return appleTree[0]
                }
            }
    //여기서 apply를 사용하는 경우 this는 가려지게 되고 this.weight은 fruteBasket이 아닌 apple을 참조한다.
            class FruitBasket {
                private var weight = 0
    
                fun addFrom(appleTree: AppleTree) {
                    val apple = appleTree.pick().let { it ->
                        this.weight += it.weight
                        add(it)
                    }
    
                }
    
                fun add(apple: Apple) {
    
                }
            }

    it


    最終的にletとdoはcontextオブジェクトをlamda変数とする.変数名が指定されていない場合は、暗黙的なデフォルト名itを使用してオブジェクトにアクセスできます.
    これはthisよりも短く、表現が読みやすいです.ただし、このような暗黙的なオブジェクトは、オブジェクトの関数またはPropertyを呼び出すときに使用できません.ただし、オブジェクトが関数呼び出しで変数として使用される場合は、contextオブジェクトをitとして使用することが望ましい.コードブロック内の様々な変数を使用するのにも適しています.
    fun getRandomInt(): Int {
        return Random.nextInt(100).also {
            writeToLog("getRandomInt() generated value $it")
        }
    }
    
    val i = getRandomInt()
    println(i)
    INFO: getRandomInt() generated value 99
    99
    また、context objctを変数として渡す場合、context objectのcustom nameをscopeで渡すことができます.
    fun getRandomInt(): Int {
        return Random.nextInt(100).also { value ->
            writeToLog("getRandomInt() generated value $value")
        }
    }
    
    val i = getRandomInt()
    println(i)

    return value


    スキャン関数は、返される値に依存します.
    アプリケーション
  • は、コンテキストオブジェクト
  • にも戻る.
  • let、run、および暗黙的lamdaの結果
  • を返します.

    Context Obejct


    applyとadoは自分のコンテキストオブジェクトを返します.したがって、call chainにサイドステップとして含めることができます.関数呼び出し後、同じオブジェクトの関数呼び出しを続行できます.
    val numberList = mutableListOf<Double>()
    numberList.also { println("Populating the list") }
        .apply {
            add(2.71)
            add(3.14)
            add(1.0)
        }
        .also { println("Sorting the list") }
        .sort()
    また、コンテキストオブジェクトを戻り関数の文として使用することもできます.
    fun getRandomInt(): Int {
        return Random.nextInt(100).also {
            writeToLog("getRandomInt() generated value $it")
        }
    }
    
    val i = getRandomInt()
    INFO: getRandomInt() generated value 55

    Lamda result


    let、run、withはramda結果を返します.したがって、変数への結果の割り当てや結果への演算の関連付けなどの操作を行う場合に使用できます.
    val numbers = mutableListOf("one", "two", "three")
    val countEndsWithE = numbers.run { 
        add("four")
        add("five")
        count { it.endsWith("e") }
    }
    println("There are $countEndsWithE elements that end with e.")
    There are 3 elements that end with e.
    他の戻り値を無視したり、一時的なスキャン変数のスキャン関数を使用したりする場合に使用します.
    val numbers = mutableListOf("one", "two", "three")
    with(numbers) {
        val firstItem = first()
        val lastItem = last()        
        println("First item: $firstItem, last item: $lastItem")
    }
    First item: one, last item: three

    Funtion


    次の例では、一般的なスタイルを定義するルールを示します.

    let


    呼び出しチェーンの結果に1つ以上の関数を呼び出すことができます.たとえば、次のコードは、2つのアクションの結果をセットに出力します.
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    val resultList = numbers.map { it.length }.filter { it > 3 }
    println(resultList)
    [5, 4, 4]
    letを使用する場合
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    numbers.map { it.length }.filter { it > 3 }.let { 
        println(it)
        // and more function calls if needed
    } 
    [5, 4, 4]
    nullのチェックに使用
    val str: String? = "Hello"   
    //processNonNullString(str)       // compilation error: str can be null
    val length = str?.let { 
        println("let() called on $it")        
        processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
        it.length
    }
    let() called on Hello
    また、コードは、可読性を向上させるために、限られた範囲のローカル変数としても使用される.このため、itではなくlambda変数名を指定する必要があります.
    val numbers = listOf("one", "two", "three", "four")
    val modifiedFirstItem = numbers.first().let { firstItem ->
        println("The first item of the list is '$firstItem'")
        if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
    }.uppercase()
    println("First item after modifications: '$modifiedFirstItem'")
    The first item of the list is 'one'
    First item after modifications: '!ONE!'

    with


    非拡張関数:コンテキストオブジェクトは引数として渡されますが、lambda内部では受信機として使用できます.戻り値はlambdaの結果です.コンテキストオブジェクトの関数とpropertyを呼び出し、ramda結果を提供しないことをお勧めします.コードは、「このオブジェクトを使用して次の操作を行います」で読み込まれます.
    val numbers = mutableListOf("one", "two", "three")
    with(numbers) {
        println("'with' is called with argument $this")
        println("It contains $size elements")
    }
    'with' is called with argument [one, two, three]
    It contains 3 elements
    もう1つの使用例は、構成または関数を使用して値を計算するアシスタントオブジェクトを導入することです.
    val numbers = mutableListOf("one", "two", "three")
    val firstAndLast = with(numbers) {
        "The first element is ${first()}," +
        " the last element is ${last()}"
    }
    println(firstAndLast)
    The first element is one, the last element is three

    run


    コンテキストオブジェクトはlambda受信機として使用できます.戻り値はramda結果です.実行実行はlet実行と同じ機能を実行するが、letをコンテキストオブジェクトの展開関数として呼び出す.(他の演算を実行して値を返す...)ramdaにオブジェクトの初期化と戻り値の計算が含まれている場合、runは非常に役立ちます.
    val service = MultiportService("https://example.kotlinlang.org", 80)
    
    val result = service.run {
        port = 8080
        query(prepareRequest() + " to port $port")
    }
    
    // the same code written with let() function:
    val letResult = service.let {
        it.port = 8080
        it.query(it.prepareRequest() + " to port ${it.port}")
    }
    Result for query 'Default request to port 8080'
    Result for query 'Default request to port 8080'
    拡張子ではなく関数として使用できます.非拡張実行では、式が必要なときに複数の文のブロックを実行できます.
    val hexNumberRegex = run {
        val digits = "0-9"
        val hexDigits = "A-Fa-f"
        val sign = "+-"
    
        Regex("[$sign]?[$digits$hexDigits]+")
    }
    
    for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
        println(match.value)
    }
    +123
    -FFFF
    88

    apply


    値を返さずに主に受信者オブジェクトのメンバーによって操作されるコードブロックについてはapplyを使用します.通常、オブジェクトを構成します.これらの呼び出しは、「オブジェクトに次の割り当てを適用」として読み込むことができます.chaingも適用できます.
    val adam = Person("Adam").apply {
        age = 32
        city = "London"        
    }
    println(adam)
    Person(name=Adam, age=32, city=London)

    also


    オブジェクトを引数として使用し、オブジェクトを返します.このオプションを使用すると、オブジェクトのプロパティや機能ではなく、オブジェクトを参照する必要がある操作や、外部の範囲内で参照オブジェクトによって覆われないようにします.コードでは、「オブジェクトに対して次の操作を行うこともできます.」このように読むことができます.
    l numbers = mutableListOf("one", "two", "three")
    numbers
        .also { println("The list elements before adding new one: $it") }
        .add("four")
    The list elements before adding new one: [one, two, three]

    リファレンス


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