TL;DR

  • ドメインの意味を DSL で直接表現すると、誤った意味をそもそも書けなくなり、偶有的複雑性を排除できる。
  • tagless-final で型安全な DSL を作り、Layered Final で多層のインタプリタをつないでクエリ DSL を最適化する。

内容

ドメインモデルと DSL

ドメインモデルは、意味の判断を使う側に委ねると意味を失い、崩壊する。意味の曖昧さは偶有的複雑性の起点になる。DSL を使えばドメインの意味を直接表現できるので、誤った意味をそもそも記述できないようにできる。つまり DSL は、意味を埋め込める型安全なインタフェースと捉えられる。モデルを言語として定式化すれば、どの操作が許され、どの解釈が正当かを型と構文のレベルで制約できる。

『人月の神話』によると、複雑さは本質的複雑性と偶有的複雑性の 2 つに大別でき、それぞれの性質に合わせた向き合い方が必要である。DSL でドメインの言葉を使って問題を直接記述すると、ビジネスの本質をそのままコードに落とし込めるので、偶有的複雑性を排除できる。

抽象の役割

抽象化をドメインモデルに適用するとは、ドメインの意図を崩さずに本質を取り出し、その意図を実装できる形で表現することである。これにより、課題の解像度と予測可能性が高まる。現実の問題空間は複雑で曖昧で、変化し続けるので安定しない。一方、解決空間では安定性と再現性が求められる。抽象は、問題空間から本質的で再現可能なパターンを取り出し、解決空間での安定性と再現性を高めることで、2 つの空間の橋渡しをする。安定依存の原則や安定度・抽象度等価の原則が言うように、より安定したものに依存させる。クリーンアーキテクチャはその例である。

抽象の特性は次の 3 つに分けられる。

  • 本質を取り出す: 意味のコア以外のノイズを除く蒸留(ドメイン知識から本質的な概念を取り出し、DSL の語彙にする。特に重要)、必要なものだけを公開する最小化(DSL に含める演算や型を厳選して誤用を減らす)、意図が伝わる表現力
  • 構造に作用する: DSL の操作を安全に合成できる合成可能性(式のネストや関数合成)、他の要素と干渉しない直交性、拡張可能性
  • 変更や検証をしやすくする: 変更容易性、テスト容易性

tagless-final

tagless-final は、型安全な埋め込み言語を作る手法である。DSL の型付けがホスト言語の型付けに帰着するので、HOAS が使え、コンポーネントを合成でき、パーサも要らない。言語は構文、型付け規則、意味論からなる。tagless-final では、構文と型付け規則を表すインタフェース(Symantics)と、意味論を与える複数のインタプリタで言語を構成する。

表現したい対象言語(Int)と、それを表すメタ言語(Repr[Int])を分離することが大事である。メタ言語から対象言語の世界に値が流れ込む経路は慎重に設計する。多層の値を受け取ると、意図しない値が紛れ込む。

tagless-final を使うと、扱いたいビジネスドメインを表現できるかを早く検証でき、偶有的複雑性を排除しながら実装できる。

Layered Final

単なるデータではなく、意味を持ったドメインの計算対象をクエリしたいので、Nested Relational Calculus(NRC)をベースにした独自のクエリ DSL を実装する。参照系が多いシステムではクエリ自体がドメインの資産になるが、SQL で書くとインフラ層に押し込められて再利用しにくい。一方で、クエリにはパフォーマンスの問題がつきものである。

Layered Final は、多層のインタプリタを与えてクエリ DSL を最適化できるようにするアプローチである。場当たり的な書き換え(Ad-hoc Rewrite)は拡張や保守が難しいので、代数的な正規化をパイプラインにして管理する。層どうしは Embedding-Projection Pair という構造でつなぐ。これは自然変換であり、Free モナドに近い。層を足しても消しても DSL 自体やユーザのプログラムには影響しないので、最適化や変換、トレーシング用のログや Provenance を後から足したいときに便利である。ユーザのプログラムや DSL を早く検証できる利点もそのまま残る。

ソフトウェアプロダクトライン

ソフトウェアプロダクトライン(SPL)では、似て非なるユースケースの差分をどう扱うかが問題になる。汎用エンジンに寄せると重厚長大になり、個別開発に寄せると保守が難しくなる。その中間として DSL が使えるかもしれず、AI と SPL と DSL を組み合わせれば、型安全なまま開発の出力を最大化できる可能性がある。

キーワード

関連