sky’s 雑記

主にAndroidとサーバーサイドの技術について記事を書きます

レシーバ付き関数リテラルのおさらい

kotlin製ライブラリを読んでいると頻出の文法だが,混乱して毎回復習してる気がする.

レシーバ付き関数リテラルの説明のためにletとapplyの比較が良くされており, 実装でも良く使う文法で馴染み深いから混乱したらここに立ち返ると良い.

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

letはラムダの実行結果が最終的な型になるが,applyはラムダで何をしようが拡張しているオブジェクト自体が返る.

"1".let{ it.toInt() + 1 } // Int -> 2
"1".let{ it.toFloat() + 1 } // Float -> 2.0
"1".apply{ this.toInt() + 1 } // String -> "1"
"1".apply{ this + "b" } // String -> "1"

上記前提があると以下のようなkotlin製ライブラリで見るtypealias使ったレシーバー付き関数リテラルも理解しやすい.

typealias Calc1 = Int.() -> Int
typealias Calc2 = (num: Int) -> Int
typealias Calc3 = Int.(num: Int) -> Int

val calc1: Calc1 = fun Int.(): Int = this
val calc2: Calc2 = fun (num: Int): Int = num
val calc3: Calc3 = fun Int.(num: Int): Int = this + num

println(100.calc1()) // 100
println(calc2(100)) // 100
println(100.calc3(100) // 200

またレシーバ付き関数リテラルを応用して以下のような手続き的なDLS記法をライブラリの実装ではよく見かける.

typealias Calc4 = Num.() -> Int
typealias Ope = Int.() -> Int

class Num {
    var n = 1000
    inline fun ope(ope: Ope): Int {
        n = n.ope()
        return n
    }
}

fun calculate(cal: Calc4): Num {
    val num = Num()
    num.cal()
    return num
}

val result = calculate {
  ope { plus(100) }
  ope { div(5) }
}

println(result.n) // 220

Kotlinのスコープ関数の定義を読み解く - Qiita