KotlinでDSLを作ってみた
DSL (Domain Specific Language) と言うとちょっと大げさだけど、試しにJSONビルダーをKotlinで作った。いきなりだけど、まずは完成品の紹介。
{ "age": 24, "name": "taro", "programming_language": [ "Java", "Kotlin" ] }
こんな感じのJSONデータを、次のようなKotlinコードで吐くことができる。
val json = jsonObject { "name" to "taro" "age" to 24 "programming_language" to jsonArray { + "Java" + "Kotlin" } } println(json.text)
このJSONビルダーの全容はGithubで。
解説
まず一見してわかるのは、jsonObjectとjsonArray。これは関数リテラルを取る関数。Kotlinには、最後に取る引数が関数リテラルの場合は上記のような略記法があるので、あたかも組み込み構文のような見た目になる。
jsonArrayのあとに続くブロックの中に注目すると、+記号と文字列がある。これはJSONの配列に要素を追加している。これは単項演算子 + をAny?の拡張関数として定義して実現している。ただし、この拡張関数のスコープを限定する工夫をしてある。関数 jsonArrayは実際には次のようなコードになっている。
public fun jsonArray(init : JsonArrayBuilder.() -> Unit) : JsonArrayBuilder { val jsonArray = JsonArrayBuilder(init) return jsonArray }
(たぶん)見慣れない関数リテラルの型宣言 JsonArrayBuilder.() -> Unit は、「引数を取らずUnitを返すJsonArrayBuilderの拡張関数としての関数リテラル」を意味する。上記の単項演算子 + の演算子オーバロードはJsonArrayBuilder内で定義しているので、結果、それが有効になるのは関数リテラル init の中ということになる。
Webフレームワーク kara に見るDSL
先日紹介したKotlin向けWebフレームワークのkaraではHTMLとCSSをKotlinで作られた言語内DSLで記述する。
s("body") { margin = box(0.px) backgroundColor = c("#000") }
上記はCSSの記述例。0.px のように数値に対しての拡張関数で直線寸法を表現することができる。関数 s のシグネチャは
internal final fun s(selector : jet.String, init : kara.styles.Element.() -> Unit) : Unit
となっている。上記のmarginとbackgroundColorは、kara.styles.Elementのプロパティであることがわかる。
まとめ
Kotlinにおいて、DSLを作るために便利な文法要素は
- 拡張関数
- 関数リテラル
- 中置呼び出し(ピリオドや括弧を伴わない関数呼び出し)
の3つ。