mizoguche.info

コードリーディングでKotlinの知識を増やしていく - Kotlinはラムダの表現力がすごくてDSL的なものつくるの簡単そう編

今回調べたこと

ktor/HelloApplication.kt at master · Kotlin/ktor
    install(Routing) {
        get("/") {
            call.respondText("Hello, World!")
        }
    }

最後の引数のラムダ

Higher-Order Functions and Lambdas - Kotlin Programming Language

Kotlinでは最後の引数が関数で、その引数にラムダを渡すとき、ラムダを丸カッコの外に書ける。

下記の2つは同じ意味。

lock(lock, { sharedResource.operation() })

lock (lock) {
    sharedResource.operation()
}
ktor/ApplicationFeature.kt at master · Kotlin/ktor
fun <P : Pipeline<*>, B : Any, F : Any> P.install(feature: ApplicationFeature<P, B, F>, configure: B.() -> Unit = {}): F {

関数 install の最後の引数の型は B.() -> Unit なので

    install(Routing) {
        get("/") {
            call.respondText("Hello, World!")
        }
    }

install の最後の引数に

  {
      get("/", {
          call.respondText("Hello, World!")
      })
  }

というラムダを渡してるということ。

引数の最後にブロックがきたらカッコの外に出せるRubyっぽい。

レシーバー付き関数リテラル

install の引数の最後の型、

ktor/ApplicationFeature.kt at master · Kotlin/ktor
fun <P : Pipeline<*>, B : Any, F : Any> P.install(feature: ApplicationFeature<P, B, F>, configure: B.() -> Unit = {}): F {
B.() -> Unit

は、 B をレシーバーとする、引数なし、返り値 Unit の関数を表す、レシーバー付き関数リテラル。

Function Literals with Receiver - Kotlin Programming Language

install の引数ラムダのレシーバー

    install(Routing) {
        get("/") {
            call.respondText("Hello, World!")
        }
    }

install の第1引数に渡している RoutingRouting クラスの companion object である。

Companion Objects - Kotlin Programming Language

Routing のcompanion objectは

ktor/Routing.kt at master · Kotlin/ktor
    companion object Feature : ApplicationFeature<Application, Routing, Routing> {

なので、 Application.install

fun <P : Pipeline<*>, B : Any, F : Any> P.install(feature: ApplicationFeature<P, B, F>, configure: B.() -> Unit = {}): F {

fun Application.install(feature: Routing, configure: Routing.() -> Unit = {}): Routing {

と読み替えるとよさそう。

よって、

        get("/") {
            call.respondText("Hello, World!")
        }

の型は Routing.() -> Unit と解決されるので、 get メソッドは Routing のメソッドである。

どのように呼び出されてるかを確認する

ktor/ApplicationFeature.kt at master · Kotlin/ktor
fun <P : Pipeline<*>, B : Any, F : Any> P.install(feature: ApplicationFeature<P, B, F>, configure: B.() -> Unit = {}): F {
// ...
                val installed = feature.install(this, configure)
// ...
}
ktor/Routing.kt at master · Kotlin/ktor
    companion object Feature : ApplicationFeature<Application, Routing, Routing> {
// ...
        override fun install(pipeline: Application, configure: Routing.() -> Unit): Routing {
            val routing = Routing(pipeline).apply(configure)
// ...
        }
// ...
    }

スコープ関数のネーミングはまだイマイチピンときてないが、 Routing のインスタンスつくってそのまま apply してるのは、まさに configureapply という感じがあって読みやすくて良い。

DSL書きやすそう

レシーバー付き関数リテラル使うとDSLがめっちゃ書きやすそうと思った。

例として Kotlin/kotlinx.html: Kotlin DSL for HTML のようなものがある。

System.out.appendHTML().html {
    body {
        div {
            a("http://kotlinlang.org") {
                target = ATarget.blank
                +"Main site"
            }
        }
    }
}

だからといってDSLをつくりまくるのは死を見る気がする。

まとめ