Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin DSLを理解してみる / Understanding Kotlin DSL

yagi2
September 18, 2018
2.3k

Kotlin DSLを理解してみる / Understanding Kotlin DSL

集まれKotlin好き!Kotlin愛好会 vol4 - 2018-09-20

slide repo : yagi2/love-kotlin-2018-09-20: LT slide @ Kotlin愛好会 vol4 2018-09-20 - https://github.com/yagi2/love-kotlin-2018-09-20

links:
Kotlin/kotlinx.html: Kotlin DSL for HTML - https://github.com/Kotlin/kotlinx.html

ngsw-taro/knit: JUnit API set for Kotlin - https://github.com/ngsw-taro/knit/

Operator overloading - Kotlin Programming Language - https://kotlinlang.org/docs/reference/operator-overloading.html

KEEP/scope-control-for-implicit-receivers.md at master · Kotlin/KEEP - https://github.com/Kotlin/KEEP/blob/master/proposals/scope-control-for-implicit-receivers.md

kotlinx.html/DSL-markers.md at dsl-markers · Kotlin/kotlinx.html - https://github.com/Kotlin/kotlinx.html/blob/dsl-markers/DSL-markers.md

Kotlin 1.1-M03 is here! | Kotlin Blog - https://blog.jetbrains.com/kotlin/2016/11/kotlin-1-1-m03-is-here/

Kotlin/anko: Pleasant Android application development - https://github.com/Kotlin/anko

yagi2/LayoutDSLSample: Let's implement DSL that create layout in Android with Kotlin! - https://github.com/yagi2/LayoutDSLSample

Kotlinイン・アクション | Dmitry Jemerov, Svetlana Isakova, 長澤 太郎, 藤原 聖, 山本 純平, yy_yank |本 | 通販 | Amazon - https://www.amazon.co.jp/dp/4839961743/

yagi2

September 18, 2018
Tweet

Transcript

  1. Kotlin DSL を理解してみる Kotlin DSL を理解してみる Kotlin DSL を理解してみる 青柳

    樹 (Itsuki AOYAGI) - シーサー株式会社 Kotlin 愛好会 Vol4 2018-09-20 1
  2. 自己紹介 自己紹介 自己紹介  青柳 樹(Ituki AOYAGI )  やぎにい(@yagi2,

    @yaginier)  Android エンジニア @ シーサー株式会社  普段は京都に居ます⛩  Kotlin 歴:1 年と半年弱 2
  3. DSL DSL DSL Domain Speci c Language Domain Speci c

    Language (ドメイン固有言語) (ドメイン固有言語) 特定の領域・分野の問題を解決するための言語。 身近なところだと  SQL  正規表現  make 4 . 1
  4. Kotlin DSL Example Kotlin DSL Example DSL は構造と文法を持つ。 普通に書いたケース val

    html = createHtml() val table = createTable() html.addChild(table) val tr = createTr() table.addChild(tr) val td = createTd() tr.addChild(td) td.text = " " 4 . 4
  5. Kotlin DSL Example Kotlin DSL Example DSL は構造と文法を持つ。 DSL を用いたケース

    ぱっと見の圧倒的わかりやすさ val html = createHtml(). table { tr { td { + " " } } } Kotlin/kotlinx.html: Kotlin DSL for HTML 4 . 5
  6. 拡張関数・拡張プロパティ 拡張関数・拡張プロパティ 既存のクラスに新しいメソッドやプロパティを生やすことが出来る。 fun String.toPackedString() = this.replace("\n", "").replace(" ", "")

    """ { "name": "yagi2", "age": 24 } """.toPackedString() // -> "{"name":"yagi2","age":24}" var <T> List<T>.lastIndex: Int get() = size - 1 listOf(1, 2, 3).lastIndex // -> 2 5 . 2
  7. メソッドの引数の最後がラムダ式であれば メソッドの引数の最後がラムダ式であれば 外側に出せる 外側に出せる ちょっとDSL っぽい見た目になる。 fun hoge(num: Int, lambd:

    () -> String) hoge(100, { "this is lambda" }) hoge(100) { "this is lambda" } fun piyo(lambda: () -> String) piyo({ "this is lambda "}) piyo { "this is lambda" } 5 . 3
  8. レシーバ指定のラムダ式 レシーバ指定のラムダ式 ラムダ式にレシーバを指定して、ラムダ式の中のスコープの をそのクラ スにする機能。 data class Fruit(val name: String,

    val num: Int) fun hoge(lambda: Fruit.() -> Unit) { val apple = Fruit(" ", 10) apple.lambda() } hoge { // this Fruit println("${this.name} ${this.num}個 ") // -> 10個 // this 省略可能 println("${name} ${num}個 ") // -> 10個 } 5 . 4
  9. レシーバ指定のラムダ式 レシーバ指定のラムダ式 スコープの関数のwith 第1 引数でT クラスを受け取り、そのT クラスを指定したラムダ式を第2 引数 で受け取る。 fun

    <T, R> with(receiver: T, block: T.() -> R): R { ... } data class Fruit(val name: String, val num: Int) val apple = Fruit(" ", 10) with(apple) { // apple(Fruit ) 渡 this Fruit println("${name} ${num}個欲 ") // -> 10個欲 } 5 . 5
  10. 中置関数 中置関数 長澤太郎さんが作られているJUnit をシンプルに書けるライブラリ での例 が中置関数になっている。 ngsw-taro/knit: JUnit API set

    for Kotlin val actual = "hoge" val expected = "hoge" actual.should be expected interface Asserter<T> { infix fun be(expected: T) } internal class AsserterImpl<T>(override val target: T) : Asserter<T> { override fun be(expected T) { target.should(`is`(expected)) } } 5 . 7
  11. 演算子オーバーロード 演算子オーバーロード Kotlin では既に用意されている数種類のオペレーターをオーバーロードして 実装することができる。    Operator overloading - Kotlin

    Programming Language data class Point(val x: Int, val y: Int) operator fun Point.plus(that: Point) = Point(this.x + that.x, this.y + that.y) val p1 = Point(10, 10) val p2 = Point(15, 20) p1 + p2 // -> Point(25, 30) // OK p1.plus(p2) // -> Point(25, 30) 5 . 8
  12. アノテーション アノテーション プロックが入れ子になった際に、内側のブロックから外側のブロックにアク セスできるようになってしまうのが多々ある。 それを防ぐためのアノテーション。 具体的には以下のようなケースを防ぐことが出来る。   html { head

    { // head 中 this this@html this@head head { } } } KEEP/scope-control-for-implicit-receivers.md at master · Kotlin/KEEP kotlinx.html/DSL-markers.md at dsl-markers · Kotlin/kotlinx.html Kotlin 1.1-M03 is here! | Kotlin Blog 5 . 9
  13. Kotlin Gradle DSL Kotlin Gradle DSL Kotlin Gradle DSL での

    の書き方 それぞれの実装を見てみる dependencies.implementation("androidx.constraintlayout:constraintlayout:1.1.3") dependencies { implementation("androidx.constraintlayout:constraintlayout:1.1.3") } 6 . 1
  14. レシーバ指定のラムダ式をとるケース レシーバ指定のラムダ式をとるケース の実態は クラスを指定したラムダ式を取っているため は を継承しているので、そ の中で呼んでる はさっきの拡張関数。 dependencies {

    implementation("androidx.constraintlayout:constraintlayout:1.1.3") } class DependencyHandlerScope(val dependencies: DependencyHandler) : DependencyHandler by depen fun Project.dependencies(configuration: DependencyHandlerScope.() -> Unit) = DependencyHandlerScope(dependencies).configuration() } dependencies { // this DependencyHandlerScope } 6 . 3
  15. Anko という、Android アプリを制作を快適にするライブラリがある その中に というDSL でレイアウトを組めるものがある linearLayout { padding =

    dip(30) textView { text = "A" }.lparams { margin = dip(30) } } Kotlin/anko: Pleasant Android application development 7 . 1
  16. Layout を作るDSL をつくる Layout を作るDSL をつくる 外側から作っていく。 linearLayout { textView

    { text = "Hello,Kotlin DSL!" textColor = Color.RED }.lparams { margin = dip(10) } } 8 . 1
  17. TextView TextView の中で を書きたい → レシーバ指定で のラムダの中の を にする これでここまで書けるようになった

    fun textView(init: TextView.() -> Unit): TextView = TextView(context).apply(init) linearLayout { textView { text = "Hello,Kotlin DSL!" setColor(Color.RED) } } 8 . 4
  18. 親View への追加 親View への追加 作った を親( ) に追加したい → メソッドを

    の拡張関数にする fun LinearLayout.textView(init: TextView.() -> Unit): TextView { val textView = TextView(context).apply(init) addView(textView) // this LinearLayout return textView } 8 . 5
  19. 親View への追加 親View への追加 メソッドを の拡張関数にしたことで の中で メソッドが呼べなくなった → の中の

    を にする これでここまで書いて、レイアウトを表示することができるようになった fun linearLayout(init: LinearLayout.() -> Unit): LinearLayout = LinearLayout(context).apply(init) linearLayout { textView { text = "Hello,Kotlin DSL!" setColor(Color.RED) } } 8 . 6
  20. LayoutParams の適用 LayoutParams の適用 メソッドを実装していく これでここまで書けるようになった fun TextView.lparams(init: () ->

    Unit): TextView { val lp = LinearLayout.LayoutParams(width, height) layoutParams = lp // this.layoutParams return this } linearLayout { textView { text = "Hello,Kotlin DSL!" setColor(Color.RED) }.lparams { } } 8 . 7
  21. LayoutParams の適用 LayoutParams の適用 のラムダ内で などをセットしたい → レシーバ指定してあげる これでここまで書けるようになった fun

    TextView.lparams(init: LinearLayout.LayoutParams.() -> Unit): TextView { val lp = LinearLayout.LayoutParams(width, height) layoutParams = lp // this.layoutParams return this } linearLayout { textView { text = "Hello,Kotlin DSL!" setColor(Color.RED) }.lparams { setMargins(dip(10)) } } 8 . 8
  22. LayoutParams の適用 LayoutParams の適用 に と を渡せるようにする(0 のため) fun TextView.lparams(

    width: Int, height: Int, init: LinearLayout.LayoutParams.() -> Unit ): TextView { val lp = LinearLayout.LayoutParams(width, height) layoutParams = lp // this.layoutParams return this } 8 . 9
  23. LayoutParams の適用 LayoutParams の適用 このままだと という感じになるので、デフォルト値を与える そして引数のラムダを適用する lparams(WRAP_CONTENT, WRAP_CONTENT) {

    setMargin(dip(10)) } fun TextView.lparams( width: Int = LinearLayout.LayoutParams.WRAP_CONTENT, height: Int = LinearLayout.LayoutParams.WRAP_CONTENT, init: LinearLayout.LayoutParams.() -> Unit ): TextView { val lp = LinearLayout.LayoutParams(width, height).apply(init) layoutParams = lp // this.layoutParams return this } 8 . 10
  24. 9 割完成 9 割完成 今までの実装で以下のように書いて表示することができるようになった , …… ? linearLayout {

    textView { text = "Hello,Kotlin DSL!" setColor(Color.RED) }.lparams { setMargins(dip(10)) } } 8 . 11
  25. プロパティのセット プロパティのセット , を , で書きたい 理想の完成形 linearLayout { textView

    { text = "Hello,Kotlin DSL!" color = Color.RED }.lparams { margin = dip(10) } } 8 . 12
  26. setColor の実装 setColor の実装 プロパティっぽくするならプロパティにするしかない → 拡張プロパティで解決 これでここまで書けるようになった var TextView.textColor:

    Int get() = currentTextColor set(value) = setTextColor(value) linearLayout { textView { text = "Hello,Kotlin DSL!" color = Color.RED }.lparams { setMargins(dip(10)) } } 8 . 13
  27. setMargins の実装 setMargins の実装 は4 方向にセットするメソッドであるが 当然だが は存在しない → 例外を投げよう!

    と書けてしまう(実行時には落ちるが) いや、不親切すぎでは…… var LinearLayout.LayoutParams.margin: Int get() = throw RuntimeException("This property does not have a getter.") set(value) = setMargins(value, value, value, value) .lparams { val currentMargin = margin } 8 . 15
  28. setMargins の実装 setMargins の実装 じゃあビルドできないようにしてあげよう → タグを レベルでつけてあげる 親切になった☺ var

    LinearLayout.LayoutParams.margin: Int @Deprecated("This propetry does not have a getter." level = DeprecationLevel.ERROR) get() = throw RuntimeException("This property does not have a getter.") set(value) = setMargins(value, value, value, value) .lparams { // val currentMargin = margin } 8 . 16
  29. 完成 完成 これで最初の完成形が動いて、レイアウトが表示できるようになった linearLayout { textView { text = "Hello,Kotlin

    DSL!" textColor = Color.RED }.lparams { margin = dip(10) } // 側 組 書 (1..10).forEach { textView { text = it.toString() } } } 8 . 17
  30. 完成 完成 DSL を作るときのコツ  そのラムダ式の中の は何になっているのか  外側から作っていく 今回作ったDSL

    のリポジトリ ブランチが実装前で完成形のDSL が存在する ブランチに実装が存在するので穴開きクイズで実装してみよう! yagi2/LayoutDSLSample 8 . 18
  31. おしまい おしまい おしまい Have a nice Kotlin! Have a nice

    Kotlin! DSL を作るときのリファレンスと このスライドのリポジトリ Type-Safe Builders - Kotlin Programming Language HTML Builder | Try Kotlin yagi2/love-kotlin-2018-09-20 9 . 1
  32. Kotlin DSL をもっと知るには Kotlin DSL をもっと知るには Kotlin イン・アクションの11 章がDSL について詳しく書かれている

    Kotlin イン・アクション | Dmitry Jemerov, Svetlana Isakova, 長澤 太郎, 藤原 聖, 山本 純平, yy_yank | 本 | 通販 | Amazon 9 . 2