Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
KotlinでDSLを作る #Kotlin_Sansan
Search
Jumpei Yamamoto
November 09, 2017
2
1.5k
KotlinでDSLを作る #Kotlin_Sansan
Jumpei Yamamoto
November 09, 2017
Tweet
Share
More Decks by Jumpei Yamamoto
See All by Jumpei Yamamoto
みんな大好き拡張関数 #kotlin_sansan
boohbah
1
8.9k
sealed class in Kotlin1.1
boohbah
1
1.6k
Sansanではたらくアプリエンジニアの20%ルール
boohbah
0
710
KotlinでDSL
boohbah
0
8.9k
ObservableArrayとPikkel
boohbah
2
1.2k
KotlinでPhantom Type #kotlin_sansan
boohbah
2
3.8k
#jkug Kotlinのclass delegation
boohbah
1
330
#DroidKaigi 既存のAndroidプロジェクトに Kotlinを導入した話
boohbah
5
2.5k
KotlinをJavaで理解する
boohbah
1
1k
Featured
See All Featured
Product Roadmaps are Hard
iamctodd
PRO
49
11k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
127
18k
Being A Developer After 40
akosma
87
590k
RailsConf 2023
tenderlove
29
940
Side Projects
sachag
452
42k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
32
2.7k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.2k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Automating Front-end Workflow
addyosmani
1366
200k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Transcript
KotlinでDSLを作る 11/09 Kotlin勉強会 at Sansan ⼭本純平
Kotlin イン・アクション 2 JetBrainsのKotlinメンバによる Kotlin解説本 今⽇はその中から 「第11章 DSLの作成」 の内容を紹介!
⾃⼰紹介 3 ⼭本純平 twitter: @boohbah github: https://github.com/yamamotoj Sansan株式会社 DSOC事業部 R&Dグループ
データ分析 Eight Android版の開発
⾃⼰紹介 - Kotlin⼊⾨までの助⾛読本 - Kotlinイン・アクション 4
Agenda - ドメイン固有⾔語のコンセプト - 内部DSLと外部DSL - DSL作成のためのKotlinの機能 - 中置関数 -
invoke関数 - レシーバー付きラムダ 5
ライブラリのAPIとして洗練されているのは? - コードで何が置きているかが明確になっていること。 - 良い名前と、良いコンセプト - 不必要な記述がなく、コードがきれいに⾒えること - これを達成する⼿段がDSL -
洗練されたAPIは⾔語に組み込みの機能と区別がつかない?
ドメイン固有⾔語(Domain Specific Language)とは? - ⇔ 汎⽤的なプログラム⾔語 - 特定のタスクやドメインに焦点を当て、それとは関係な い機能を削ぎ落としたプログラム⾔語 -
外部DSLと内部DSLがある 7
外部DSL - 動作するアプリケーションとは別の独⽴した⾔語 - 独⾃に字句、構⽂解析を⾏い、必要な機能を実装する - 特定の分野に最適化される反⾯、ホストのプログラムとのやりとりが煩雑 8 # SQL
SELECT * FROM user_table WHERE name = ’hoge’; # 正規表現 http://[¥w¥d/%#$&?()~_.=+-]+
内部DSL - アプリケーションの汎⽤⾔語を、特定のドメイン向けにそれっぽく使う - 同じ⾔語で記述されているので、やり取りもスムーズ - 表現能⼒(== 読みやすさ)は元の⾔語に依存する - 静的型付⾔語の場合、コンパイル時に構⽂のチェックされる
9
静的型付⾔語でDSLを作る上でのポイント - 適切にドメインの語彙を使⽤しているか? - いかに読み⼿が(余計な複雑さなしに)⾃然にその⾔語を読み下せるか? - 型付けによっていかに適切にIDEの補完を効かせることができるか? 10
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” } } }
Kotlinで内部DSLの作成
DSL作成のために使えるKotlinの機能 - レシーバー付きラムダ - invoke規約 - 中置関数 - 演算⼦のオーバーロード -
getメソッドの規約 - 拡張関数 - メンバ拡張 13 - 括弧の外側のラムダ引数 - 委譲プロパティ - 委譲クラス - 静的型付け - @DslMakerアノテーション
DSLでよく使われるKotlin機能ランキング
DSLでよく使われるKotlin機能ランキング 第三位
DSLでよく使われるKotlin機能ランキング 第三位 中置関数
中置関数とは? 17 // 関数定義 infix fun <A, B> A.to(that: B)
= Pair(this, that) // Pairを⽣成する 1.to(“one”) // 通常の呼び出し 1 to “one” // 中置呼び出し 関数の宣⾔にinfixとつけることで、ドットや()なしで関数を呼び出せる
中置呼び出しのDSLへの適⽤ 18 kotlintestライブラリより s should startWith(“kot”)
中置呼び出しのDSLへの適⽤ 19 s should startWith(“kot”) infix fun <T> T.should( matcher:
Matcher<T>) = matcher.test(this) shouldを中置関数として定義
中置呼び出しの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
中置呼び出しの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インターフェイスを実装したクラス
中置呼び出しのDSLへの適⽤ 22 s should startWith(“kot”) Matcher interfaceを引数にとる中置関数
中置呼び出しのDSLへの適⽤ 23 s should startWith(“kot”) Matcher interfaceを引数にとる中置関数 Matcher interfaceを実装したクラス
さらに、こんな書き⽅も 24 s should start with “kot”
さらに、こんな書き⽅も 25 s should start with “kot” shouldはStringの拡張関数で中置関数 infix fun
String.should(x: start) = StartWrapper(this)
さらに、こんな書き⽅も 26 s should start with “kot” infix fun String.should(x:
start) = StartWrapper(this) object start startはobjectとして宣⾔ object: シングルトンのクラス宣⾔
さらに、こんな書き⽅も 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クラスのメソッド
さらに、こんな書き⽅も 28 s should start with “kot” startオブジェクトを引数にとり StartWrapperクラスを返す中置関数
さらに、こんな書き⽅も 29 s should start with “kot” startオブジェクトを引数にとり StartWrapperクラスを返す中置関数 StartWrapperクラスのメソッド
中置関数
ポイントは object 宣⾔ 30 s should start with “kot” infix
fun String.should(x: start) = StartWrapper(this) object start objectは通常⼤⽂字で始まるが、ここでは⼩⽂字で開始される
ポイントは object 宣⾔ 31 s should end with “lin” infix
fun String.should(x: end) = EndWrapper(this) object end startの代わりにendというobjectも定義可能 should関数も、endを引数にとるオーバーロード
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
ポイントは 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”)
ポイントは 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”
ポイントは 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”
DSLでよく使われるKotlin機能ランキング 第⼆位
DSLでよく使われるKotlin機能ランキング 第⼆位 invoke規約
Kotlinの規約 - operator修飾⼦をつけて関数(拡張関数)を定義すると、対応す る演算⼦を呼び出したときのその関数に変換される 38 class Foo(val value: Int) operator
fun Foo.plus(that: Foo) = this.value + that.value Foo(1) + Foo(2) // => Foo(3) plus関数が「+」演算⼦に対応
いろいろな規約 39 演算⼦ 対応する関数 a - b minus a *
b times a / b div ++ a inc a == b equals >, <, =>, =< compareTo a[index] get val (a, b) = p componentN
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関数が対応する
invoke規約 41 operator fun Foo.invoke(f: ()->Unit) = f() val foo
= Foo() foo.invoke({ }) foo({ }) foo{ }
gradle Kotlin DSLの例 42 dependencies.compile("junit:junit:4.11") dependencies { compile("junit:junit:4.11") } この2つを同時に実現したい
通常の呼び出し 43 class DependencyHandler { fun compile(coordinate: String) { println("Added
dependency on $coordinate") } } dependencies.compile("junit:junit:4.11")
{ } でネストされた呼び出し 44 dependencies { compile("junit:junit:4.11") }
invoke()を追加 45 class DependencyHandler { fun compile(coordinate: String) { println("Added
dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { body() } }
invoke()を追加 46 class DependencyHandler { fun compile(coordinate: String) { println("Added
dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { body() } } レシーバー付きラムダ
invoke()を追加 47 class DependencyHandler { fun compile(coordinate: String) { println("Added
dependency on $coordinate") } operator fun invoke(body:DependencyHandler.() -> Unit) { this.body() } } レシーバー付きラムダ body()はDependencyHandlerをthisとした 拡張関数として呼び出される
gradle Kotlin DSLの例 48 dependencies.invoke( { this.compile("junit:junit:4.11") } ) operator
fun invoke(body:DependencyHandler.() -> Unit) { this.body() }
gradle Kotlin DSLの例 49 dependencies { compile("junit:junit:4.11") }
2つの呼び出しを同時に実現 50 dependencies.compile("junit:junit:4.11") dependencies { compile("junit:junit:4.11") }
DSLでよく使われるKotlin機能ランキング 第⼀位
DSLでよく使われるKotlin機能ランキング 第⼀位 レシーバー付きラムダ
詳細は 53 Kotlin イン・アクションにて
54 $PQZSJHIU4BOTBO *OD"MMSJHIUTSFTFSWFE 4BOTBOҰॹʹ৽͍͠ՁΛ࡞͍ͬͯ͘ ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$
ʢ8FCΞϓϦέʔγϣϯʣ J04"OESPJEΞϓϦ ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ໊σʔλԽࢄॲཧγεςϜ ๏ਓ໊͚ཧαʔϏεʮ4BOTBOʯ ๏ਓ໊͚ཧαʔϏε ʮ4BOTBOʯ ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ΤϯδχΞืूத 4BOTBO࠾༻ ݕࡧ SFDSVJU!TBOTBODPN·Ͱ ͓ؾܰʹ͝࿈བྷ͍ͩ͘͞ɻ ڵຯͷ͋Δํ
Enjoy! 55