最近、ビジネスアプリケーションの世界でもDSLが注目を集めているようです。
Martin FowlerもDSLについての本を執筆中で、ドラフトを公開しています。
言語屋出身の私にとって、DSLの世界もなかなか興味深いので、簡単にまとめておきます。デザインパターンと同じように、全く新しい概念というわけではなく、既存の概念をうまく整理したものですね。
なお、Fowlerのサイトを参考にしていますが、本のドラフトに忠実に書いているわけではないので注意してください。
DSLとは
Domain Specific Languageの略。対語はGeneral Purpose Language(汎用言語)。 ある特定の目的のために特別にデザインされた言語。目的を達成するための機能さえあればよく、チューリング完全である必要もない。その代わり、目的を達成するために簡潔に記述できる。
DSLの利点をひとことで言えば、特定の目的のために無駄を省いて簡潔に記述できるところにある。
DSLは新しい概念ではなく、昔からいろいろな形で存在する。 例として、正規表現、SQLなどがある。正規表現は文字列処理というドメイン専用の言語であるし、SQLはデータベース操作が目的の言語である。これらの言語は「何でもできる」言語ではなく、ある特定用途に特化して簡潔に記述できるので、DSLである。
DSLは他の言語(例:Java)の中で使われることもある。 ただし、Javaそれ自体は汎用言語なので、DSLではない。
ライブラリやフレームワークとの違いは、ライブラリが"Push button API"とすれば、DSLは"Fluent API"だということである。 Push Button APIは、通常それ自身で意味的に完備(例:getPrice)であるが、Fluent APIはコンテキストに強く依存する(例:price)ことが多い。
品物を定義し、その値段を設定する例を書いてみる。
Push Button API (Java風味)
Item pen = new Item();
pen.setPrice(100);
Fluent API (Ruby風味)
pen :price => 100
どちらも同じことができるが、Fluent APIはノイズが少ない(余計なことを極力書かない)のが特徴である。
ここでは文字列表記をしているが、DSLは文字列であることもあれば、図表であることもある。MDD(Model Driven Development)で利用するモデル図も一種のDSLとみなすことができる。
DSLの種類
Fowlerの本に準じた種類わけは以下の通り。
External DSL
処理する言語(ホスト言語)とは別に作成したもの。例えば特定目的のスキーマ下のXMLもExternal DSLである(例: Antで使うbuild.xml)。
目的のために構文が自由に決められるため、シンプルに記述できる。 ノイズは通常きわめて少ないが、XMLのように多い場合もある。
当然専用のパーサが必要になる。
Internal DSL (Embedded DSL)
ホスト言語内に記述したもの。動的言語のRubyやLisp、マクロやテンプレートなどのsyntactic sugarが豊富な言語では比較的容易に実現可能だが、Javaなどでも可能。
記述はホスト言語が処理するので、専用のパーサは不要。ただし、ホスト言語の構文を逸脱できないので、ノイズは多くなりがち(括弧、セミコロン、キーワード、一時的な変数など)。
最近のJavaはsyntactic sugarが多く私はあまり好きではないが、この場合は有効に使える。
Javaで実現する場合、Method Chaining、Function Sequence、Annotation、Nested Functionなどのテクニックを使う。 ただ、それぞれ長所・短所があり、どれが良いと一概に言うことはできない。
たとえば、Method Chainingは比較的実現しやすいが、ノイズは結構多くあまり読みやすくない。Function Sequenceをstatic importとともに使うとよりシンプルに記述できるが、グローバルにメソッドや情報を持たなければいけないという弱点もある。
これらのハイブリッドで実現することもある。その場合はノイズを極力減らせるが、記述する内容によって記述方法が変わるので、分かりにくくなる(特に記述しにくくなる)可能性もある 。
Language workbench
DSLによる開発をするための環境を含んだ考え方。DSLは保存される表現、実行されるときの表現、そして編集するときの表現があるが、それらを中心となる抽象表現(≒Semantic Model)で表す。
DSLとそうでないものの境界はあいまい。一般用途の言語でも、特定用途に限って使うように制限されていればDSLと言えることもある。DSLでも、それが本来の目的以上の一般用途に使われればDSLとは言えない。
DSLでは、Semantic Modelと記述形式(構文)、実行形式は分離していることが望ましい。 保守が楽になるためである。
DSLはインタプリタ形式で実行されることもあれば、コンパイラ形式(コード生成)で実行されることもある。 Semantic Modelが分離していれば、これはあまり大きな問題ではない。 インタプリタ形式の場合はSemantic Modelを直接実行できるが、 コンパイラ形式にする場合はコード生成を行うので、 コード生成器の作成労力やパフォーマンス、実行環境(インタプリタを積めるかどうか)などのトレードオフである。
XMLは特定目的のスキーマの上でDSLとみなすこともできるし、そうでない場合もある。 Javaなどではパーサを作るのが簡単なのでXMLをDSLとして使うことが多いかもしれないが、表現上はタグだらけになる(=ノイズが多くなる)ので読み書きしにくいかもしれない。 これは独自構文のパーサを作る労力とDSLユーザの労力のトレードオフである。XMLを専用の編集ツールで隠蔽する手もあるが、これはLanguage workbenchのアプローチに近くなる。
コミュニケーションツールとしてのDSL
DSLはコミュニケーションツールとしての側面も持つ。
開発者にとってコミュニケーションを容易にすることももちろんだが、ユーザ(特にドメインエキスパート)とのコミュニケーションを容易にすることも可能である。 この場合、ドメインエキスパートはDSLを書くよりも読むことがきることがより重要。 もちろん書ければそれに越したことはないが、ドメインエキスパートはプログラマではないことに注意。
DSLを使うべきとき、そうでないとき
DSLの開発にはコストがかかるので、それに見合わないときは作るべきではない。
また、利用者がDSLを覚えるのに苦労するようでも意味がない。 複雑なライブラリの上にDSLを作って簡単に使えるようにする、などのメリットがなければならない。
機能範囲を絞ることも重要。 悪い例はAntで、相次ぐ機能拡張でわかりにくくなってしまった。
DSLが複雑なときは、場合によっては屋上屋を重ねることも有用。 以前から、設定ファイル(一種のDSL)ではこのようなことが行われてきた。 例えばsendmail.conf->CF、Makefile->imakefileなど。
DSLの作り方
DSLの作り方には、既存のライブラリのAPIに屋上屋を重ねる(ラップする)方法、ドメインエキスパートと作るモデルから入る方法、言語仕様から入る方法(特にembeddedの場合)などがある。
既存の言語のままでもDSLと言えることもある。
たとえば、COBOLのCOPY句を読み込んでJavaとのインタフェース用のプログラムを出力する場合、このCOPY句をJavaとのインタフェース用DSLと「みなす」ことも可能(COBOL言語仕様の一部のみを特定目的で使っていることがポイント)。
Fowlerの本では、パーサやコンパイラ、コード生成などについても解説していますが、この辺は特に目新しいことはないので知ってる人は読む必要はないでしょう。
匿名
画面が真っ暗、でもカーソルは出てる状況。
探して、ここにたどり着きました。
パスワード入力で、復活!
修理に出す寸前でした。ホントにありがとう!