DSL / Choosing What to Generate


Choosing What to Generate

要約

  • コード生成時に最初に決めることは、どんな種類のコードを生成するつもりであるか、ということ。
  • コード生成で使えるふたつのスタイルがある。
    • Model-Aware Generation
    • Model Ignorant Generation
    • これらの違いは「対象となる環境」のセマンティックモデルの明確な表現を持つかどうかにある。


  • 状態マシンの例を考える。
  • 状態マシンを実装する二つの典型的な選択肢がある。
    • ネストした条件
    • 状態テーブル
  • 非常に単純な状態モデル、例えばネストした条件付きアプローチを考えるなら次のような感じ。
public void handle(Event event) {
  switch (currentState) {
    case ON:
      switch (event) {
        case DOWN:
          currentState = OFF;
      }
    case OFF:
      switch (event) {
        case UP : currentState = ON;
      }
  }
}
  • 一方が他方にネストした二つの条件付きテストがある。
  • 外側の条件はマシンの現在の状態を見て、内部の条件は受け取ったイベントでスイッチしている。
  • 状態マシンのロジックが言語の制御フローの中に埋め込まれているので、Model Ignorant Generationの例であり、セマンティックモデルの明確な表現は存在しない。


  • Model-Aware Generationを用いることで、生成コードの中にセマンティックモデルの何らかの表現をいれる。
  • 使用された「DSLプロセッサ」とまったく同じである必要はないが、データ表現の何らかの形式になる。(意味不明)
    • 状態マシンは少し複雑になる。
class ModelMachine...
  private State currentState;
  private Map<State, Map<Event, State>> states = new HashMap<State, Map<Event, State>>();
  public ModelMachine(State currentState) {
    this.currentState = currentState;
  }
  void defineTransition(State source, Event trigger, State target) {
    if (! states.containsKey(source)) states.put(source, new HashMap<Event, State>());
    states.get(source).put(trigger, target);
  }
  public void handle(Event event) {
    Map<Event, State> currentTransitions = states.get(currentState);
    if (null == currentTransitions) return;
    State target = currentTransitions.get(event);
    if (null != target) currentState = target;
  }
  • ネストしたマップとして遷移を保持している。
    • 外部のマップは状態のマップで、キーは状態名、値は二つ目のマップ。
    • 内部のマップはキーはイベント名、値は対象の状態。
  • ありのままの状態モデル‐明確な状態、遷移、イベントのクラスを持たなかったとしても‐であるが、データ構造は状態マシンのふるまいを取りこんでいる。
  • データ駆動であることの結果として、このコードは完全に包括的で、特定のコードによってそれを実行するよう設定される必要がある。
modelMachine = new ModelMachine(OFF);
modelMachine.defineTransition(OFF, UP, ON);
modelMachine.defineTransition(ON, DOWN, OFF);
  • 生成コードにセマンティックモデルの表現を入れることによって、生成コードは包括的なフレームワークコードと特定の設定コード間の冒頭で述べたことと同じ分割を得る。(意味不明)
  • Model Ignorant Generationが制御フローでセマンティックモデルを表現することによって一般/特定を一緒くたに扱う一方、Model-Aware Generationは一般/特定の分離を維持する。


  • Model-Aware Generationを使用するなら、生成する必要のある唯一のコードは特定の設定コードである。
    • 「対象となる環境」で基礎となる状態マシンを完全に構築でき、そこでテストできる。
  • Model Ignorant Generationでは、もっと多くのコードを生成しなければならない。
    • 生成する必要のないライブラリ関数の中にいくつかのコードを抜きだすことができるが、ほとんどの重要なふるまいは生成されなければならない。


  • 結果として、Model-Aware Generationを使用してコード生成するのはとても簡単になる。
    • 生成されたコードは通常非常に単純。
    • 包括的なセクションを作らなければならないが、コード生成システムに依存することなく実行とテストを行えるので、通常はとても簡単。


  • Fowler自身は(当然と言えば当然だが)Model-Aware Generationをできる限り使用する。
  • が、できないときがある。
    • 対象となる言語がモデルをデータとして容易に表現できないとか。
    • 処理に限界があるとか。
    • 組み込みシステムはModel Ignorant Generationを使用することもあるが、それはModel-Aware Generationでのコード生成による処理のオーバーヘッドがとても大きいときとか。


  • 意識すべきもう一つのことはModel-Aware Generationを使用できるかどうかということである。
  • システムの特定のふるまいを変える必要があるなら、設定コードに対応した人工物のみを置き換えることができる。
  • ここでC言語のコードを生成する例を考える。
  • 設定コードを包括的なコードではなく異なるライブラリに入れることができる。
    • (やり遂げるためにはいくらかの実行時バインディングメカニズムを要するであろうが、)システム全体を置き換えることなく特定のふるまいを入れ替えることができる。


  • 実行時に完全に読まれることが可能な式を生成することもできる。
    • 例えばシンプルなテキストテーブル。
off switchUp on
on switchDown off
  • 起動時にデータファイルをロードするコードを持つ一般的なシステムを犠牲にして、実行時にシステムの特定のふるまいを変えることができる。


  • 「対象となる環境」でパースするもう一つのDSLを生成していると思われるだろうがそうではない。
    • 人間が扱うためにデザインされたものではないので、上の小さなテーブルは本当のDSLではない。
    • テキスト形式は人間が読むことができるが、デバッグすることに対してはより有益である。
    • 主にパースしやすいようにデザインされたもので、それで対象となるシステムに速やかにロードすることができる。
    • このような形式でデザインするときは、人間の可読性<<<<<<<パースの容易さ。
  • DSLでは人間の可読性が最優先。

ファウラーへのフィードバック

担当者のつぶやき

  • ほぼ全訳っぽいので直す時間があればそのうち。。。

みんなの突っ込み