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.8k
sealed class in Kotlin1.1
boohbah
1
1.6k
Sansanではたらくアプリエンジニアの20%ルール
boohbah
0
700
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
320
#DroidKaigi 既存のAndroidプロジェクトに Kotlinを導入した話
boohbah
5
2.5k
KotlinをJavaで理解する
boohbah
1
1k
Featured
See All Featured
Gamification - CAS2011
davidbonilla
80
5k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Designing for Performance
lara
604
68k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
126
18k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.2k
Designing on Purpose - Digital PM Summit 2013
jponch
115
7k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
27
840
How GitHub (no longer) Works
holman
310
140k
Art, The Web, and Tiny UX
lynnandtonic
297
20k
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