レシーバ付き関数リテラルのおさらい
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