DSL / Class Symbol Table


DSL

タイトル

一言要約

  • IDE の補完機能などを使用するには、静的型付け言語であることが必須である。
  • 通常の DSL ではそれが使用できないが、Class Symbol Table を使用することで、静的な型付けが可能になる。
  • その結果、DSL が歪曲する場合もある。

要約

  • 近代的なIDEを使うと、型を使った補完(名前の後ろにピリオドを入力すると、指定可能なメソッド名の一覧を表示するなど)をしてくれます。
  • 通常、動的型付け言語やDSLの場合、そういう機能を利用できません。
  • しかし、クラスシンボルテーブルを使うことで、そのような機能が利用できるようになります。
  • 基本は、汎用的なExpression Builderクラスの、サブクラスにDSLスクリプトを書くことです。
  • そのDSLスクリプトの例 (Tasks というサブクラスのメソッド内に記述します)
    task("drinkCoffee").dependsOn("make_coffee", "wash");
  • このようにフィールドを定義する事により、IDEはフィールドを補完し、コンパイラーはチェックするようになります。
  • しかしながら、フィールドを定義するだけでは、十分ではありません。
  • DSLスクリプト内で、DSLのフィールドを参照する際は、フィールドの定義ではなく内容を参照します。
    • (unlockDoor.code("D1UL"); idle.actions(unlockDoor) の場合 "D1UL" を参照するということ?)
  • クラスシンボルテーブルを作るためには、フィールド定義へのリンクがランタイム時に必要です。
    • (普通に、code() で中身をお参照するだけの話では?)
  • スクリプトが実行される前に、適切なオブジェクトを持った個々のフィールドを実装することにより、これを提供することができます。
    • フィールドとスクリプトを生成するためのコードを作る。
    • シンボルテーブルの特徴として、フィールド名はキーのように振舞い、フィールド名はキーのように振舞います。
    • これをするには、通常、リフレクションを使います。
  • 要点
    • DSL言語要素の完全に静的な型付けを提供するということ。
    • その結果、静的な型付けに基づく洗練されたツールを、IDEが使用することができる。
    • タイプシステムの中に適合するためにDSLを、かなり歪曲しなければならない。
    • これらの制約は、DSLを読んだり使用したりすることを、より困難にするかもしれません。

(ごちゃごちゃ言うより、コードを見たほうが早いかも)

public class BasicStateMachine extends StateMachineBuilder {

  Events doorClosed, drawOpened, lightOn, panelClosed;
  Commands unlockPanel, lockPanel, lockDoor, unlockDoor;
  States idle, active, waitingForLight, waitingForDraw, unlockedPanel;
  ResetEvents doorOpened;

  protected void defineStateMachine() {
    doorClosed. code("D1CL");
    drawOpened. code("D2OP");
    lightOn.    code("L1ON");
    panelClosed.code("PNCL");

    doorOpened. code("D1OP");

    unlockPanel.code("PNUL");
    lockPanel.  code("PNLK");
    lockDoor.   code("D1LK");
    unlockDoor. code("D1UL");

    idle
        .actions(unlockDoor, lockPanel)
        .transition(doorClosed).to(active)
        ;

    active
        .transition(drawOpened).to(waitingForLight)
        .transition(lightOn).   to(waitingForDraw)
        ;

    waitingForLight
        .transition(lightOn).to(unlockedPanel)
        ;

    waitingForDraw
        .transition(drawOpened).to(unlockedPanel)
        ;

    unlockedPanel
        .actions(unlockPanel, lockDoor)
        .transition(panelClosed).to(idle)
        ;
  }
}

class StateMachineBuilder...
  public StateMachine build() {
    initializeIdentifiers(Events.class, Commands.class, States.class, ResetEvents.class);
    defineStateMachine();
    return produceStateMachine();
  }
  abstract protected void defineStateMachine();

  private void initializeIdentifiers(Class... identifierClasses) {
    List<Class> identifierList = Arrays.asList(identifierClasses);
    for (Field f : this.getClass().getDeclaredFields()) {
      try {
        if (identifierList.contains(f.getType())) {
          f.setAccessible(true);
          f.set(this, Identifier.create(f.getType(), f.getName(), this));
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }

  protected void definingState(States identifier) {
    if (null == start) start = identifier.getState();
  }

  private StateMachine produceStateMachine() {
    assert null != start;
    StateMachine result = new StateMachine(start);
    for (States s : getStateIdentifers())
      s.produce();
    produceResetEvents(result);
    return result;
  }

  private List<States> getStateIdentifers() {
    return getIdentifiers(States.class);
  }
  private <T extends Identifier> List<T> getIdentifiers(Class<T> klass) {
    List<T> result = new ArrayList<T>();
    for (Field f : this.getClass().getDeclaredFields()) {
      if (f.getType().equals(klass))
        try {
          f.setAccessible(true);
          result.add(((T) f.get(this)));
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }
    }
    return result;
  }

  private void produceResetEvents(StateMachine result) {
    result.addResetEvents(getResetEvents());
  }
  private Event[] getResetEvents() {
    List<Event> result = new ArrayList<Event>();
    for (Events identifier : getIdentifiers(ResetEvents.class))
      result.add(identifier.getEvent());
    return result.toArray(new Event[result.size()]);
  }
}

class Identifier...
  private String name;
  protected StateMachineBuilder builder;

   public Identifier(String name, StateMachineBuilder builder) {
    this.name = name;
    this.builder = builder;
  }
  public String getName() {
    return name;
  }

  static Identifier create(Class type, String name, StateMachineBuilder builder)
      throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException
  {
      Constructor ctor = type.getConstructor(String.class, StateMachineBuilder.class);
      return (Identifier) ctor.newInstance(name, builder);
  }

public class Events extends Identifier {
  private Event event;
  public Events(String name, StateMachineBuilder builder) {
    super(name, builder);
  }
  Event getEvent() {
    return event;
  }

class Events...
  public void code(String code) {
    event = new Event(getName(), code);
  }
}

class States...
  private State content;
  private List<TransitionBuilder> transitions = new ArrayList<TransitionBuilder>();
  private List<Commands> commands = new ArrayList<Commands>();

  public States(String name, StateMachineBuilder builder) {
    super(name, builder);
    content = new State(name);
  }

  public States actions(Commands... identifiers) {
    builder.definingState(this);
    commands.addAll(Arrays.asList(identifiers));
    return this;
  }

  public TransitionBuilder transition(Events identifier) {
    builder.definingState(this);
    return new TransitionBuilder(this, identifier);
  }

  void produce() {
    for (Commands c : commands)
      content.addAction(c.getCommand());
    for (TransitionBuilder t : transitions)
      t.produce();
  }

class TransitionBuilder...
  private Events trigger;
  private States targetState;
  private States source;

  TransitionBuilder(States state, Events trigger) {
    this.trigger = trigger;
    this.source = state;
  }

  public States to(States targetState) {
    this.targetState = targetState;
    source.addTransition(this);
    return source;
  }

  void produce() {
    source.getState().addTransition(trigger.getEvent(), getTargetState().getState());
  }

(うーん、でも、結構、ややこしい。)

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

  • typo: particuarly = particularly
  • typo: workign = working
  • typo: disapears = disappears
  • typo: restricitons = restrictions

担当者のつぶやき

  • やっていることは、なんとなくわかったけれども、Class Symbol Table ではない実装と比較しないと、その利点がいまいち腹落ちしない感じがする。
  • そもそも、どこの部分が Class Symbol Table なのか、よく分からなかった。

みんなの突っ込み