概要
Kotlin DSL で,パスをスラッシュ区切り(operator fun div
)で書けるようにしてみました.
パスは単純に "path1" / "path2" / "path3"
のように記述することもできますし,
"path1" / {
- "path1-1" / "path1-1-1"
- "path1-2" / {
- "path1-2-1"
- "path1-2-2"
}
}
のように木構造を書くこともできます.
実装はこちらです.
https://github.com/hmiyado/kuri
背景
Kotlin Developers Meetup に参加しました.
そこで,Hadi 氏によるライブ Kotlin DSL 構築や,LT での有限オートマトンのDSLによる記述といった発表を見て,自分でも少し書いてみようと思いました.
何を書くかで思い当たったのが,パスの組み立てです.
普段は Android を書いていますが,URIを組み立てるために,パスの記述が各所に記述が散らばってしまっていました.
URI全体の一覧性がない,各所でタイプミスのリスクがある,等の問題があります.
まず,手始めに,一覧性がない問題を解決するため,木構造を持ったパスを記述できるようにしました.
実装
パスの連結の実装
最も重要な実装は operator fun div
を用いた文字列の拡張です.
operator fun String.div(path: String): KPath
これで,文字列の後に /
を置いてパスを連結することができます(KPath
は木構造でないパスを保持するクラスです).
/
を連続して書いてパスを連結させるためには,KPath
自身も div
を実装する必要があります.
operator fun KPath.div(path: String): KPath
木構造の実装
パスの連結の実装によって, /
の後に文字列を書いてパスを連結していくことができるようになりました.
木構造を書くためには, /
の後にラムダ式を取れるようにする必要があります.
"path" / { /* ラムダ式 */ }
木構造を保持するクラスを KNode
,そのビルダーを KNode.Builder
としましょう.
以下の拡張関数を実装すれば,見た目を合わせることができます.
operator fun String.div(builder: KNode.Builder.()->Unit):KNode = KNode.Builder().apply(builder).build()
これで木構造自体は書けるようになりました.
次に,木構造の中身,子のパスを増やす部分を書くために,ラムダ式の中に目を向けてみましょう.
"path" / {
- "subpath1"
- "subpath2"
}
文字列の前に -
がついています.
これは, 子のパスを追加するために付けざるを得なかった記号で,kotlinでいうところの operator fun unaryMinus
を使っています.
operator fun String.unaryMinus(): KPath
この拡張関数の中で, KNode.Builder
が内部的に保持するコレクションに,文字列を追加するという処理を行っています.
もしこれがなければ,以下のような書き方になりますが,
"path" / {
"subpath1"
"subpath2"
}
これだと単にラムダ式の中に文字列が2つあるというだけになってしまい,ビルダーに文字列をパスとして追加するタイミングがありません.
無理やり unaryMinus
をつけることで,なるべくDSLの見た目に違和感を出さないようにしつつ,ビルダーにパスを登録できるようにしています.
感想
DSLの見た目から逆算して各演算子を実装したため,内部的な実装がきれいになりませんでした.
特に,木構造を記述するためにビルダーのプロパティに値をセットするのではなく,演算子(-
)がついていればコレクションに追加するという実装になっています.
そのあたりの関係で,結構煩雑な実装を強いられる箇所がありました.
プロパティを明示せずにきれいに DSL を書く知見がありましたらぜひ教えてください.
今回の実装は,構造をパースできるようにしてみた,という状態で終わっており,まだ実用に耐えうる段階にはありません.
将来的には,上記DSLで定義したパスから,クラスファイルを自動生成する,パスの中にID等の変数を許容し,型安全にパスを生成できるようにする,等の発展的実装をしたいです.