目次

概要

ある程度まとめ。 (2010年における現状。)

具体例の辺りはそれなりに開発経験ないと難しいかも。

DSL の使われ方

DSL の目指すところは、突き詰めると「アプリケーション開発を楽にする」だとは思うんですが、 その在り方にはいろんな形があるかと思います:

  • 非プログラマーが直接実行可能な「ドメインモデル」を書く。

    • 開発において、直接的なコード書きよりも、顧客からの要件を吸い出してモデル化(ドメインモデリング)するのが主な仕事になる人がいて、 そういう層が直接実行可能なものを書けるようにする。

    • 注意: 現実には、プログラマーが不要という意味ではありません。例えば、以下のような用途が「落としどころ」かもしれません。

      • ドメインモデリングを担当する人とプログラマーのコミュニケーションを円滑にするための共通言語にする。

      • あるいは、プログラミングが主業務だった人もドメインモデリングに関われるようにする。

    • あるいは、ディジタルネイティブがそろそろ就職しだす昨今、顧客側にもある程度プログラムの読み書きが必要かも:

      • 少なくとも委託で開発してもらったものの良し悪しの判断つく程度に。

      • 委託先と保守契約でもめても、引き継ぎ先を探すまでの間くらい自前で保守できるように。

  • プログラマーがより「意図通り」にプログラムを書けるようにする。

    • もっとプログラミング作業を簡単に。

    • コードを書くだけが仕事だったような人も、もっと上流の設計を容易にできるように。

DSL を作る上での注意

学習コスト

既存の汎用言語とは異なる言語を作る以上、学習コストが発生します。 ただし、DSL の学習コストは大きめに見積もられる傾向があります。

  • ドメインモデリングの結果、DSL を作らずとも、何らかのライブラリを作ってドメインモデルを表現することになるはず。

    • このライブラリにも、モデルを理解するための学習コストが少なからず発生する。

    • モデルの学習コストとの差分で考えると、DSL の学習コストはそこまで大きくないはず。

  • ただし、汎用言語のライブラリとして実装した場合、IDE による支援を期待できる。

    • 例えば、C# のライブラリとして実装すれば、Visual Studio の IntelliSense の補助を受けつつ学習できる。

    • DSL 学習のコストは、この IDE 支援の受けやすさの差と考えるべきかも。

実装コスト

外部 DSL を作る場合、構文解析を作る手間がかかります。 また、言語の学習には IDE への対応まで考える必要があり、 ここまで考え出すと実装コストは非常に高くなります。

まずは内部 DSL の実装から考えてみるべきでしょう。 C# などの言語では、「オブジェクト初期化子」や「ラムダ式」のような機能もあり、 内部 DSL の記述もあまり煩雑にならずに実現可能かと思われます。 Java などでも、いわゆる fluent interface(流ちょうなインターフェイス、流れるようなインターフェイス)と呼ばれているような書き方などを駆使して、 意外とすっきりとした内部 DSL を作れるはずです。

また、DSL は、用途によっては、純然たるデータを表現するものも多くあります。 (要するに、データベースに記録できるような情報を DSL で書くもの。 ロジックを全く含まない。) この手の DSL なら、XML のようなマークアップ言語を使うべきでしょう。 XML を使って、スキーマも定義しておけば、IDE による補完や検証などのサポートも受けられます。

DSL 自体の修正

なまじ便利な DSL を作ると、何でもかんでもその DSL の範囲内で書こうとして、トリッキーなコードを書いてしまうことがあります。 要求範囲が変わって、現在の DSL の記述力では足りなくなった場合には、DSL 自体の修正を考えましょう。

そのためには、DSL 自体(構文解析やコード生成する部分)の保守性が非常に重要になります。 DSL 自体の修正頻度はあまり高くありません。 だからといって、「めったに修正しないものだから多少見づらくても平気」とはなりません。 めったに修正しないからこそ、忘れやすく、読みやすい記述を心がけないと保守できなくなります。

独自路線になるのを避ける

DSL を使っているうちに、DSL の機能がどんどんと増え、気が付けば汎用言語で行うべき様なことまで DSL に実装してしまうことがあります。 これは、必要もないのに新しい言語を増やし、学習コストを上げてしまうだけなので避けるべきことです。

既存の汎用言語でできることは汎用言語でやるべきで、 まずは、DSL と汎用言語の混在開発を考えましょう。

また、DSL の文法を考える際、普段から使い慣れている言語に近づけることを考えましょう。 例えば、普段使っている言語が Java や C# なら、 ブロックの区切りは {} を、コメントは // を使うのが一番でしょう。

自然言語に近づけない

「既存の汎用プログラミング言語よりも、自然言語に近づけよう」というのはあまりうまくいきません。

  • 自然言語は、文脈の共有(今何の話をしているとか、相手がどこまで知っているかお互いわかっているとか)を前提としています。 それは、機械言語には期待できません。

  • 自然言語は、音声メディアを通すことを前提としていて、文にする場合には実はあまりいい文法ではありません。 文にする場合には () などの記号を使った方がすっきりします。

  • 人と人とのコミュニケーションは、普段から言葉以外のものも使っています。 文章だけの資料で、ホワイトボードも使わない会議の不毛さを想像してください。 DSL を作る際には、このような(図表などの)言葉に表れない表現まで念頭に入れておくべきでしょう。

DSL の歴史

1990年代

この頃はあまり DSL という言葉は使われていませんでしたが、 似たような考え方(メタプログラミング)はありました。

1990年代は、LISP などの言語でメタプログラミングが流行ったものの、 あまり一般的にはなっていません(LISP = マイナー言語)。 技術が未熟だったというのもあると思いますし、 メタプログラミングの欠点の部分が許容できないくらい深刻でした。

  • パフォーマンス上の問題: DSL を効率的に実装するのが難しく、DSL 利用 = パフォーマンス低下になっていました。 また、現在と比べるとコンピューターの性能が低く、パフォーマンス低下の影響が深刻でした。

  • 普通のプログラミングとメタプログラミングの両立が難しかった: 当時は、「メタプログラミングが簡単な言語は、逆に普通のプログラミングの難しい言語」でした。 両立が無理なら、「普通」の側に傾くのも仕方のないことです。

  • 書きやすいけど読みづらい: DSL は往々にして、作った人にしか読めない状況を生みます。 アプリケーションの開発規模が大きくなるにつれて、書きやすさよりも読みやすさの方が重要になり、 結果として DSL の利用が考えづらくなります。

要するに、DSL を作る側も使う側も、もっと楽に効率的に作れて、もっと楽に読み書きできる状況が必要です。

2000年代前半

2000年代前半は、DSL = Visual 言語に偏向していたように思えます。 Visual 言語というのは、ツール上で視覚的に見れて、ドラッグ&ドロップなどの操作で開発を行えるものです。 これは、1990年代の問題の「読みづらさ」の解消と、 「汎用言語でできないこと = 視覚的なモデリング」という意識が強かったためだと思われます。

この時代の DSL の問題点としては以下のようなものがありました。

  • 「オレオレ」化: 個々の製品としてはある程度 DSL 利用で成功と言えるものがあったもの、 製品間の連携が難しかった。

  • Visual 言語偏重:

    • 作る側のハードルが高かった。 テキストベースの DSL の比ではなく難しい。

    • 使う側としても、必ずしも Visual 言語が使いやすいわけじゃない。 非開発者層としても、マウスでポチポチやるよりもテキストベースで記述したい人は多い

    • 「読みづらさ」の緩和のためにツールのサポートは必須とはいえ、 何も視覚化だけがツールサポートじゃない。

      • テキストベースでも、キーワードのハイライトや、コード補完、リアルタイム構文チェックなどのサポートの仕方もある。
2000年代後半

そこで、2000年代も後半に近づくにつれ、テキストベースの DSL が見直されるようになります。 また、複数の DSL 間での相互連携という課題も残されました。

まず、テキストベースの DSL に関して、1つの集大成とも思えるのが 「WPF」 の XAML かもしれません。

  • テキスト + 視覚化: ドラッグ&ドロップによる開発もできるし、テキストベースで記述することもできます。 一方の変更が他方に即座に反映されます。

  • ハイライト: コードは色分けされていて、どの部分がどういう意味を持つかが一目でわかります。

  • コード補完: テキストを途中まで入力するだけで、補完機能が働いて、入力を省略できます。 また、「今、どういう入力が可能なのか」が分かるので、学習も容易になります。

  • リアルタイム構文チェック: 入力にエラーがあれば、その場に即座に警告が出ます。 これも、開発を容易にすると同時に、学習のハードルを下げます。

WPF におけるテキスト+視覚化 DSL 開発
WPF におけるテキスト+視覚化 DSL 開発

DSL 間の連携に関しては、2つの方法が考えられます。 というか、DSL の用途として2つのものが考えられます。

  • DSL をデータ定義として使う:

    • 最終的に、DSL はデータとして解釈され、データベースなどに格納されます。

    • この場合、DSL 間の連携の仕方としては2つの方法が:

      • データベースへの格納の仕方を標準化する。

      • データの取り出し方を標準化する。 (現在、こちらの方が主流になっていると思います。)

  • DSL を実行可能なプログラムとして使う:

    • 最終的に、DSL はコンパイルされて実行可能ファイルになったり、インタープリター的に動的に実行されます。

    • この場合、DSL 間の連携は:

      • コンパイル結果を共通化する: 一度汎用プログラミング言語のコースコードを生成して使ったり、 .NET Framework の IL(中間言語)のような「共通基盤」になっているものにコンパイルする。

      • 動的に実行するためのライブラリを整備する: 共通のライブラリを用いて実行すれば、ある程度の相互運用も容易になる。

DSL の今後

以上のような背景を踏まえて、今後、DSL の方向性としては:

  • ツールによるサポート前提

  • Visual かテキストかと言われると、両方(ハイブリッド)

  • 作る側にも使う側にも「共通基盤」「相互連携のための標準」みたいなものが必要

一例として考えてみたのが以下のようなスライド(PowerPoint 形式(.pptx))

実現には、以下のようなものがそろう必要があると思います。 個別にみるとだいぶ出そろっていて、案外そう遠くない未来に統合的に実現されるかもしれない。

  • 文法定義用のライブラリ

    • 関数型言語の多くが、型マッチング機能や Quote という、文法定義に向いた構文を持っています。

    • MGrammarというような、C 系統の言語(Java や C#)に慣れ親しんだ人が読みやすい形式の文法定義言語なども出てきています。 (おそらくこれがそのまま主流になることはないものの、C# などの汎用言語に影響を与える可能性はあります。)

  • 共通のデータ表現形式

    • 今後、様々なデータソースに対して、読み書きするための仕組みが標準化されていくと思われます。

    • Microsoft が推進するODataや Google の推進するGDataなどが有力です。

  • プログラム実行の共通基盤

    • 静的な言語なら .NET Framework のような基盤がすでにありますし、動的言語向けにはDLR(Dynamic Language Runtime)もあります。

    • LLVMのように、コンパイラ作成の共通基盤となるような取組もあります。

  • IDE の拡張

    • Visual Studio 2010 ではプラグインの作成・配布を容易にする仕組みが提供されました。

こんな環境できるといいな

こんな DSL かければいいのに(願望)という例。

書きたい DSL

データ定義用に、以下のような DSL を書けるようになりたい。

module TypeDefinition.Models
{
    // 特性データ
    type Characteristics
        requres Norm >= 1
    {
        // 特性 X
        X : double
        requres 0 <= X && X <= 1;

        // 特性 Y
        Y : double
        requres 0 <= Y && Y <= 1;

        // 特性 Z
        Z : double
        requres 0 <= Z && Z <= 1;

        // 一次ノルム
        Norm = X + Y + Z;

        // 変更の適用操作
        command Submit excuted on this.Valid;
    }
}

ちょっと恣意的な型なのでわかりにくいですが、以下のようなもの:

  • 元データとして X, Y, Z の3つの double を持ってる。

  • そこから導出される値として Norm を計算できる。

  • X, Y, Z はいずれも 0~1 という制約付き。

  • さらに、Norm が1以上という制約も付いてる。

  • Submit というコマンドを持っていて、こいつはデータがきちんと制約を満たしていないと実行できない

生成したいコード

こいつからこんなソース吐き出したいなぁ(願望)。

  • データ処理用(C#)

    • 不変(初期化時以外、値が変化しない)で、かつ、コンストラクター内でデータ検証。

    • なので、コンストラクターさえ通ればデータの有効性保証。

  • GUI 用(C#)

    • ViewModel 用(この例は Silverlight)

      • 要は、GUI 用には「プロパティの値が変更された」とかを通知する機能が必要で、 その辺りを実装したものを ViewModel と呼ぶ。
    • 変更通知の仕組みは手動で書くにはかなり大変で、 自動的にプロパティ間の依存関係を検出して通知コードを生成してくれる仕組みが望まれる。

      • INotifyPropertyChanged.PropertyChanged 地獄。

        • Norm = X + Y + Z みたいな記述から、プロパティの依存関係を見て、PropertyChanged の呼び出しを自動的に追加。
      • ICommand.CanExecuteChanged 地獄。

      • INotifyDataErrorInfo 地獄。

    • 付属品(C#)

この DSL のポイント
  • GUI 向けに使うにしろ、データ処理用に使うにしろ、大体共通して書くのは:

    • プロパティ名、型、データ検証属性。

    • あと、DSL 中の注釈もそのまま DisplayName 属性とか ///<summary> コメントに反映されて欲しい。

  • 検証まわり

    • GUI 用

      • ユーザーからの入力をいったんなんでも受けつけた上で、データ検証通らないと Submit コマンド実行できないようにしたい。

      • どのプロパティで検証エラーが発生しているかを見れるようにしたい。

    • データ処理用

      • GUI が絡む部分以外はいっそ 不変にしておく方が人的ミスも減るし、最適化もかけやすいはず。

      • one-phase 構築して(コンストラクターで一気に初期化して、以後、個別のプロパティ変更を認めない)、データ検証通らない状態を認めない

  • 元データ以外に、導出される値があるはず。

    • ViewModel にする場合には、X の変更を Norm の変更としても PropertyChanged イベント起こさないとダメ。

    • 変更の通知はプロパティ間の依存だけじゃなくて、コマンドの実行可否にも影響するので、ICommand.CanExecuteChanged イベントも必要。

  • 複数のデータにまたがるデータ検証もある。

で、やっぱり IDE の支援が欲しい

前述のとおり、構文解析やコード生成周りのライブラリはすでに結構充実していて、 ここで例示したようなコードの自動生成も、現状でできなくはない話です。 でも、以下のような物もないと完成とは言えないと思います。

  • IDE 支援:

    • 構文ハイライト欲しい。

    • コード補完機能が欲しい。

    • リアルタイムに構文チェックする機能が欲しい。

  • DSL の作成支援機能がもっと欲しい:

    • 「式」の部分まで自作したくなく、C# の式をそのまま使いたい。

    • 自分で C# の構文解析をしなくても、syntax View = Identifier '=' CSharp.Expression;というような記述で構文解析できるように、標準で文法定義を持っていて欲しい。

  • 生成物の中間レイヤーが欲しい:

    • DSL から直接 C# コードを生成するよりは、 一度、構文木のようなデータ構造を通したい。

    • そこまで持っていけば、コード生成の部分はライブラリがやってくれたり、 IDE 支援もやってくれたりという状態になって欲しい。

更新履歴

ブログ