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

KotlinでDSLを作る #Kotlin_Sansan

Jumpei Yamamoto
November 09, 2017
1.4k

KotlinでDSLを作る #Kotlin_Sansan

Jumpei Yamamoto

November 09, 2017
Tweet

Transcript

  1. DSLは構造と⽂法をもつ val html = createHTML() val table = createTable() html.addChild(table)

    val tr = createTR() table.addChild(tr) val td = createTD() tr.addChild(td) td.text = ”cell” 11 val html = createHTML(). table { tr{ td { + “cell” } } }
  2. DSL作成のために使えるKotlinの機能 - レシーバー付きラムダ - invoke規約 - 中置関数 - 演算⼦のオーバーロード -

    getメソッドの規約 - 拡張関数 - メンバ拡張 13 - 括弧の外側のラムダ引数 - 委譲プロパティ - 委譲クラス - 静的型付け - @DslMakerアノテーション
  3. 中置関数とは? 17 // 関数定義 infix fun <A, B> A.to(that: B)

    = Pair(this, that) // Pairを⽣成する 1.to(“one”) // 通常の呼び出し 1 to “one” // 中置呼び出し 関数の宣⾔にinfixとつけることで、ドットや()なしで関数を呼び出せる
  4. 中置呼び出しのDSLへの適⽤ 19 s should startWith(“kot”) infix fun <T> T.should( matcher:

    Matcher<T>) = matcher.test(this) shouldを中置関数として定義
  5. 中置呼び出しのDSLへの適⽤ 20 s should startWith(“kot”) infix fun <T> T.should( matcher:

    Matcher<T>) = matcher.test(this) interface Matcher<T>{ fun test(value: T) } Matcher: 値のアサーションを⾏うためのinterface
  6. 中置呼び出しのDSLへの適⽤ 21 s should startWith(“kot”) class startWith(val prefix: String) :

    Matcher<String> { override fun test(value: String) { if (!value.startWith(prefix)) throw AssertionError(“error”) } } startWithはMatcherインターフェイスを実装したクラス
  7. さらに、こんな書き⽅も 26 s should start with “kot” infix fun String.should(x:

    start) = StartWrapper(this) object start startはobjectとして宣⾔ object: シングルトンのクラス宣⾔
  8. さらに、こんな書き⽅も 27 s should start with “kot” class StartWrapper(val value:

    String) { infix fun with(prefix: String) = if (!value.startsWith(prefix)) throw AssertionError(“error”) } withはshouldで返されたStartWrapperクラスのメソッド
  9. ポイントは object 宣⾔ 30 s should start with “kot” infix

    fun String.should(x: start) = StartWrapper(this) object start objectは通常⼤⽂字で始まるが、ここでは⼩⽂字で開始される
  10. ポイントは object 宣⾔ 31 s should end with “lin” infix

    fun String.should(x: end) = EndWrapper(this) object end startの代わりにendというobjectも定義可能 should関数も、endを引数にとるオーバーロード
  11. shouldまでだとどのオーバーロードが使われるか決定さ れていない infix fun <T> T.should( matcher: Matcher<T>) = matcher.test()

    infix fun String.should(x: end) = EndWrapper(this) infix fun String.should(x: start) = StartWrapper(this) s should
  12. ポイントは object 宣⾔ infix fun <T> T.should( matcher: Matcher<T>) =

    matcher.test() infix fun String.should(x: end) = EndWrapper(this) infix fun String.should(x: start) = StartWrapper(this) s should startWith(“kot”)
  13. ポイントは object 宣⾔ infix fun <T> T.should( matcher: Matcher<T>) =

    matcher.test() infix fun String.should(x: end) = EndWrapper(this) infix fun String.should(x: start) = StartWrapper(this) s should start with “kot”
  14. ポイントは object 宣⾔ infix fun <T> T.should( matcher: Matcher<T>) =

    matcher.test() infix fun String.should(x: end) = EndWrapper(this) infix fun String.should(x: start) = StartWrapper(this) s should end with “lin”
  15. いろいろな規約 39 演算⼦ 対応する関数 a - b minus a *

    b times a / b div ++ a inc a == b equals >, <, =>, =< compareTo a[index] get val (a, b) = p componentN
  16. invoke規約 40 class Foo operator fun invoke() = print(“foo”) operator

    fun invoke(s: String) = print(s) val foo = Foo() foo() // foo foo(“hoge”) // hoge ()をつかった呼び出しに対する規約。invoke関数が対応する
  17. 通常の呼び出し 43 class DependencyHandler { fun compile(coordinate: String) { println("Added

    dependency on $coordinate") } } dependencies.compile("junit:junit:4.11")
  18. invoke()を追加 45 class DependencyHandler { fun compile(coordinate: String) { println("Added

    dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { body() } }
  19. invoke()を追加 46 class DependencyHandler { fun compile(coordinate: String) { println("Added

    dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { body() } } レシーバー付きラムダ
  20. invoke()を追加 47 class DependencyHandler { fun compile(coordinate: String) { println("Added

    dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { this.body() } } レシーバー付きラムダ body()はDependencyHandlerをthisとした 拡張関数として呼び出される
  21. gradle Kotlin DSLの例 48 dependencies.invoke( { this.compile("junit:junit:4.11") } ) operator

    fun invoke(body:DependencyHandler.() -> Unit) { this.body() }
  22. 54 $PQZSJHIU˜4BOTBO *OD"MMSJHIUTSFTFSWFE  4BOTBO͸Ұॹʹ৽͍͠Ձ஋Λ࡞͍ͬͯ͘ ஥ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$

    ʢ8FCΞϓϦέʔγϣϯʣ J04"OESPJEΞϓϦ   ݸਓ޲໊͚ࢗ؅ཧΞϓϦʮ&JHIUʯ   ໊ࢗσʔλԽ෼ࢄॲཧγεςϜ   ๏ਓ޲໊͚ࢗ؅ཧαʔϏεʮ4BOTBOʯ   ๏ਓ޲໊͚ࢗ؅ཧαʔϏε ʮ4BOTBOʯ   ݸਓ޲໊͚ࢗ؅ཧΞϓϦʮ&JHIUʯ ΤϯδχΞืूத 4BOTBO࠾༻ ݕࡧ SFDSVJU!TBOTBODPN·Ͱ ͓ؾܰʹ͝࿈བྷ͍ͩ͘͞ɻ ڵຯͷ͋Δํ͸