Kotlin で書く DSL


Kotlin での DSL の書き方についてまとめました。

Kotlin は 内部の分離されたDSL作成を支援してくれます。 DSL作成では特に Lambda を活用することになります。 DSL の分類について知りたい方は DSL, DOMAIN SPECIFIC LANGUAGE の分類 をご参照ください。

DSLを作成する際の書き方はいくつかありますが、 Kotlin のドキュメントの TypeSafeBuilders に倣ってクラスを作成します。 他に、 Kotlin DSL from Theory to Practice のように Builderクラスを作って組み合わせるやり方もあります。

サンプルコード

DSL を記述するためのクラス・関数を用意します。

このように書くと、次のように クラスA のオブジェクトを作ることができます。

上記サンプルコードの問題点

上のサンプルコードに DSL の基本が詰まっています。 しかしこのままでは、次のようなコードも記述可能です。 ルール上可能ですが、非常に見通しが悪くなります。

具体的な問題項目は次の通りです。

  • B, C を定義するブロックの中で A の属性が代入されている。
  • B を定義するブロックの中で、さらに B を定義する関数とそのブロックが記述されている。

@DslMarker

Kotlin 1.1 から導入されているアノテーション @DslMarker を用いて上の問題を解決できます。 @DslMarker はDSLを作成する際の、スコープを制御するために使えるアノテーションです。 このアノテーションは、アノテーションクラスに付与します。

@DslMarker を付けたアノテーションクラス(DslMarkerTest)を作成し、 DSLを生成するクラスに付与します。 クラス A, B にアノテーションDslMarkerTestを付与します。

このようにすると、次のようなコードはエラーとなります。

@DslMarker を付与したアノテーション @DslMarkerTest を付与した2つ以上のクラスのオブジェクトが暗黙的レシーバ、すなわち this になりうる場合、それらのオブジェクトのうち一番最後に this になるオブジェクト以外は this として使えません。

上のサンプルコードでは、 メソッドaに渡すラムダの中では @DslMarkerTest のついたクラスAのインスタンスが this となっていますが、 this.b に渡すラムダの中では新しく @DslMarkerTest のついたクラスBのインスタンスが this となるため、 その前に this となっていた クラスAのインスタンスは this としては使えなくなります。