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

KotlinでDSL

 KotlinでDSL

Jumpei Yamamoto

December 13, 2016
Tweet

More Decks by Jumpei Yamamoto

Other Decks in Programming

Transcript

  1. Copyright © Sansan, Inc. All rights reserved. > KotlinでDSL Jumpei

    Yamamoto 2016.12.13 第4回 Kotlin勉強会 in Sansan #kotlin_sansan
  2. Copyright © Sansan, Inc. All rights reserved. > ⾃⼰紹介 -

    ⼭本純平 - Sansan株式会社 Eight事業部 - EightのAndroidアプリの開発 - twitter: @boohbah - github: https://github.com/yamamotoj
  3. Copyright © Sansan, Inc. All rights reserved. > Pikkel •

    AndroidでIcePickのように状態の保存ができるKotlinの ライブラリ • https://github.com/yamamotoj/Pikkel
  4. Copyright © Sansan, Inc. All rights reserved. > Pikkel class

    MainActivity : Activity(), Pikkel by PikkelDelegate() { var data by state<String>(“”) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) restoreInstanceState(savedInstanceState) } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) saveInstanceState(outState) } }
  5. Copyright © Sansan, Inc. All rights reserved. > Agenda -

    テーマ:KotlinでDSL - DSLとは - KotlinでDSLの実践
  6. Copyright © Sansan, Inc. All rights reserved. > DSL ドメイン固有⾔語

    - Domain Specific Language - “特定のドメインに集中し、限定された表現⼒ を備えたコンピュータプログラミング⾔語” by 「ドメイン特化⾔語」マーチン・ファウラー著 - ⼤きく分けて外部DSLと内部DSLがある
  7. Copyright © Sansan, Inc. All rights reserved. > 外部DSL -

    動作するアプリケーションとは別の独⽴した⾔語 - 独⾃に構⽂解析、字句解析を⾏い、必要な機能を実装す る。 - 基本的に独⾃の構⽂を持つが、XML等の他の⾔語の構⽂を 利⽤することもある。 CSSの例
  8. Copyright © Sansan, Inc. All rights reserved. > 内部DSLとは -

    アプリケーションの⾔語を 特定のドメイン向けにそれっ ぽく使う。 - 表現能⼒ (== 読みやすさ)はホスト⾔語の制約に依存 - 「流れるようなインターフェイス」⊂ 内部DSL - 動的型付⾔語(Rubyとか)と静的型付⾔語(Javaとか Kotlinとか)で実装の⽅法が異なる
  9. Copyright © Sansan, Inc. All rights reserved. > 静的型付け⾔語で >

    DSLを作る上でのポイント - 適切にドメインの語彙を使⽤しているか? - 読み⼿が(余計な複雑さなしに)いかに⾃然に⾔語を読み 下せるか? - 型付けによって以下に適切にIDEの補完を効かせることが できるか?
  10. Copyright © Sansan, Inc. All rights reserved. > JavaのBuilderパターン People

    tom = new People.Builder() .name(“Tom”) .age(12) .hobby(“Baseball”) .build(); tom.hello(); これも⽴派なDSL new や .Builder()….build()等 ドメインとは無関係の記述が複雑 ()とか;とか冗⻑
  11. Copyright © Sansan, Inc. All rights reserved. > Kotlin 名前付き引数

    / デフォルト引数 class People( name:String, age:Int, hobby:String = “” ) val tom = People( age = 12, name = “tom” ) 引数に名前をつける ことで、読み⼿がど の値を設定している かが⼀⽬瞭然 設定する順番も変え られる optionalな項⽬ (hobby)は省略可
  12. Copyright © Sansan, Inc. All rights reserved. > SMC (

    State Machine Compiler) //State Start { // Transition / Next State / Action(s) TO_ZERO Zero {} } Zero { TO_ONE One {} } One { Return Start {} } http://smc.sourceforge.net/ 状態遷移をDSL記述してJavaやRubyなどのコードを⽣成 外部DSLとして実装 状態の定義 イベント名 遷移先の状態 アクション
  13. Copyright © Sansan, Inc. All rights reserved. > DSLを実装する⼿順 -

    DSLによって実現したいモデル構造(セマンティックモデ ル)を定義する。 - どういう構⽂を定義するかを考える - 流れるようなインターフェイス? - 階層構造 - 構⽂の評価、改善を繰り返す
  14. Copyright © Sansan, Inc. All rights reserved. > SMC (

    State Machine Compiler ) セマンティックモデル StateMachine State State State … Event Event … Event
  15. Copyright © Sansan, Inc. All rights reserved. > Kotlinでモデル構造の定義 class

    StateMachine( var currentState: State, val states: Map<String, State>) class State( val name: String, val events: Map<String, Event>) class Event( val nextState: String, val action: (()-> Unit)?)

  16. Copyright © Sansan, Inc. All rights reserved. > SMCの構⽂ //State

    // Transition / Next State / Action(s) Start { TO_ZERO Zero {} } Zero { TO_ONE One {} } One { Return Start {} } 可能な限りこれをKotlinで表現してみる
  17. Copyright © Sansan, Inc. All rights reserved. > Kotlinで定義したSMCの構⽂ val

    sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } } 状態の定義 イベント名 遷移先の状態 アクション
  18. Copyright © Sansan, Inc. All rights reserved. > Kotlin SMC

    val sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } } この構⽂がどのように実装されるか解説していきます。
  19. Copyright © Sansan, Inc. All rights reserved. > SMCを実装するクラス構成 StateMachineBuilder

    (内部DSLの実装) StateBuilder (内部DSLの実装) EventBuilder (内部DSLの実装) 1 * 1 * StateMachine State Event 1 * 1 * build() build() build()
  20. Copyright © Sansan, Inc. All rights reserved. > StateMachineの定義 val

    sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } } class StateMachine { companion object{ fun define(def:StateMachineBuilder.() -> Unit) : StateMachine { /* … */}
 }
 
 } 引数にラムダ式がひとつだけの場合は()を省略可 レシーバー指定の関数型 StateMachineBuilderの拡張関数を引数にとる StateMachineBuilderにて他のクラスの拡張関数を定義し ている場合にその拡張関数のスコープを限定することがで きる
  21. Copyright © Sansan, Inc. All rights reserved. > StateMachineBuilderの実装 class

    StateMachineBuilder { private val states = mutableMapOf<String, StateBuilder>() private var initialStateName:String? = null operator fun String.invoke(define: StateBuilder.() -> Unit){ 
 states[this] = StateBuilder(this).apply(define) 
 if(initialStateName == null){ 
 initialStateName = this } }
 
 }

  22. Copyright © Sansan, Inc. All rights reserved. > invoke operator

    class A { operator fun invoke(){ print(“this is a”) } } val a = A() a() // this is a A()() // this is a
  23. Copyright © Sansan, Inc. All rights reserved. > invoke operator

    class A { operator fun invoke( arg: String){ print(“this is a with $arg”) } } val a = A() a(“foo”) // this is a with foo A()(“bar”) // this is a with bar
  24. Copyright © Sansan, Inc. All rights reserved. > invoke operator

    // ֦ுؔ਺ͱ࣮ͯ͠૷ operator fun String.invoke(){ print(“this is $this”) } “foo”() // this is foo
  25. Copyright © Sansan, Inc. All rights reserved. > invoke operator

    // ϥϜμࣜΛҾ਺ʹͱΔ operator fun String.invoke(f: String -> Unit){ f(this) } “foo”{ print(it) }
  26. Copyright © Sansan, Inc. All rights reserved. > StateMachineBuilderの実装 class

    StateMachineBuilder { private val states = mutableMapOf<String, StateBuilder>() private var initialStateName:String? = null operator fun String.invoke(define: StateBuilder.() -> Unit){ 
 states[this] = StateBuilder(this).apply(define) 
 if(initialStateName == null){ 
 initialStateName = this } }
 
 }
 invoke operator Ϩγʔόʔࢦఆͷؔ਺ܕ
  27. Copyright © Sansan, Inc. All rights reserved. > invoke operatorを使った構⽂

    val sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } }
  28. Copyright © Sansan, Inc. All rights reserved. > StateBuilderの実装 class

    StateBuilder(val name:String) { private val events = mutableMapOf() infix fun String.transitTo(to:String): EventBuilder { val eventBuilder = EventBuilder(this, to) events[this]= eventBuilder return eventBuilder
 } }

  29. Copyright © Sansan, Inc. All rights reserved. > 中置関数 class

    A { infix fun shows(arg:String){ print(arg) } } val a = A() a.shows(“bar”) a shows “bar”
  30. Copyright © Sansan, Inc. All rights reserved. > StateBuilderの実装 class

    StateBuilder(val name:String) { private val events = mutableMapOf<String, EventBuilder>() infix fun String.transitTo(to:String): EventBuilder { val eventBuilder = EventBuilder(this, to) events[this]= eventBuilder return eventBuilder
 } fun build() = State(name, events.map { it.key to it.value.build() }.toMap())
 }
 String型の拡張関数
  31. Copyright © Sansan, Inc. All rights reserved. > 中置関数を使った構⽂ val

    sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } }
  32. Copyright © Sansan, Inc. All rights reserved. > EventBuilderの実装 class

    EventBuilder(val name:String, val transitTo: String){ var action:(()->Unit)? = null infix fun action(action:()-> Unit){ this.action = action } 
 fun build(): Event = Event(transitTo, action) } ラムダ式を引数にとる中置関数
  33. Copyright © Sansan, Inc. All rights reserved. > 中置関数を使った構⽂ val

    sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } } EventBuilder 中置関数は左結合!
  34. Copyright © Sansan, Inc. All rights reserved. > Kotlin SMC

    完成 val sm = StateMachine.define { “Start"{ "TO_ZERO" transitTo "Zero" action { } } “Zero"{ "TO_ONE" transitTo "One" action { } } “One"{ "RETURN" transitTo "Start" action { } } }
  35. Copyright © Sansan, Inc. All rights reserved. > KotlinでのDSLの資料 -

    Anko - https://github.com/Kotlin/anko - KotlinでDSLを作るときに活躍する5⼤⾔語機能 @ngsw_taro - http://taro.hatenablog.jp/entry/ 2013/12/25/070324 - KotlinでイケてるDSLを作る @kikuchy - http://qiita.com/kikuchy/items/ 7c8893c4e7e11dafdb6c
  36. $PQZSJHIU˜4BOTBO *OD"MMSJHIUTSFTFSWFE  4BOTBO͸Ұॹʹ৽͍͠Ձ஋Λ࡞͍ͬͯ͘ ஥ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$ ʢ8FCΞϓϦέʔγϣϯʣ

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