DSL / Function Sequence


Function Sequence(関数シーケンス)

一言要約

関数呼び出しを連続させる手法。 通常は、Function SequenceよりもObject Scopingを使うべき。 Function SequenceはDSLのエントリポイントとみなせる。

要約

仕組み

関数間のデータの関係は、Context Variablesを使って保持する。

Function Sequenceではグローバルな関数呼び出しを使うのが簡単だが、グローバルな関数群とstaticな解析データの2つの問題が生じる。

グローバルな関数はstatic importのような機能がないとDSLにノイズを加える。

staticなデータは特にマルチスレッドの環境で問題になる(どこから使われるているか確信できない)。

有効な解決策は代わりにObject Scopingを使うこと。

いつ使うべきか

実はあまり役に立たない。Context Variablesを使うのは不恰好でコードを複雑にする。

複数の高レベルな文(statement)があり、そこからContext Variablesと単一の結果リストを求めるのであれば、それがFunction Sequenceを使用する機会。 言語のトップレベルやNested Closureの内部でのトップレベルにおいて使える。

Function SequenceはDSLのエントリポイントとみなせる。たとえ、呼び出しが1つしかなくても。

シンプルなFunction Sequenceの代替はLiteral List。


例:簡単なコンピュータの設定( Java)

コードのインデントは必須ではない。

本当はObject Scopingを使うのがいいけど、ここではSingletonを使ってFunction Sequenceを実現する。 Singletonを使うことで、diskを保持するためのリストと2つのContext Variables(currentProcessorとcurrentDisk)を一緒に保持できる。 (これらをそれぞれstaticなフィールドに保持するよりも管理しやすくなる)

名前の衝突がいやなので、speedという関数名ではなくdiskSpeedという関数名にしている。

public class FunctionSequenceExample {

    public static void main(String[] args) {
        computer();
        processor();
          cores(2);
          processorType(i386);
        disk();
          diskSize(150);
        disk();
          diskSize(75);
          diskSpeed(7200);
          diskInterface(SATA);

        Computer computer = Builder.getComputer();
    }
}

class Builder {

    private static int DEFAULT_CORES = 1;

    private static Builder instance;

    private Processor currentProcessor;
    private Disk currentDisk;
    private List<Disk> loadedDisks = new ArrayList<Disk>();

    static void computer() {
        instance = new Builder();
    }

    static void processor() {
        instance.currentProcessor = new Processor(DEFAULT_CORES, null);
    }

    static void cores(int arg) {
        instance.currentProcessor =
            new Processor(arg, instance.currentProcessor.getType());
    }

    static void processorType(Processor.Type arg) {
        instance.currentProcessor =
            new Processor(instance.currentProcessor.getCores(), arg);
    }

    static void disk() {
        if (instance.currentDisk != null) {
            instance.loadedDisks.add(instance.currentDisk);
        }
        instance.currentDisk =
            new Disk(Disk.UNKNOWN_SIZE, Disk.UNKNOWN_SIZE, null);
    }

    static void diskSize(int arg) {
        instance.currentDisk =
            new Disk(arg, instance.currentDisk.getSpeed(), instance.currentDisk
                .getIface());
    }

    static void diskSpeed(int arg) {
        instance.currentDisk =
            new Disk(instance.currentDisk.getSize(), arg, instance.currentDisk
                .getIface());
    }

    static void diskInterface(Disk.Interface arg) {
        instance.currentDisk =
            new Disk(instance.currentDisk.getSize(), instance.currentDisk
                .getSpeed(), arg);
    }

    private Disk[] disks() {
        List<Disk> result = new ArrayList<Disk>();
        result.addAll(loadedDisks);
        if (currentDisk != null) {
            result.add(currentDisk);
        }
        return result.toArray(new Disk[result.size()]);
    }

    public static Computer getComputer() {
        return instance.currentComputer();
    }

    private Computer currentComputer() {
        return new Computer(currentProcessor, disks());
    }

}

class Computer {

    private final Processor processor;

    private final Disk[] disks;

    public Computer(Processor processor, Disk[] disks) {
        this.processor = processor;
        this.disks = disks;
    }

    public Processor getProcessor() {
        return processor;
    }

    public Disk[] getDisks() {
        return disks;
    }
    
}

class Disk {

    public static final int UNKNOWN_SIZE = -1;

    private final int size;

    private final int speed;

    private final Interface iface;

    public Disk(int size, int speed, Interface iface) {
        this.size = size;
        this.speed = speed;
        this.iface = iface;
    }

    public int getSize() {
        return size;
    }

    public int getSpeed() {
        return speed;
    }

    public Interface getIface() {
        return iface;
    }

    public enum Interface {
        SATA, SAS
    }
}

class Processor {

    private final int cores;

    private final Type type;

    public Processor(int cores, Type type) {
        this.cores = cores;
        this.type = type;
    }

    public int getCores() {
        return cores;
    }

    public Type getType() {
        return type;
    }

    public enum Type {
        i386, i586, i686
    }
}

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

担当者のつぶやき

  • static importを使うことでグローバル関数の問題が避けられるかのように書いてありますが、スコープが縮小されるわけではないし、それはないんじゃないかと思いました。クラス名を省略できるのでノイズが減ることはわかりますが。
  • 「Function SequenceはDSLのエントリポイント」だと明言されているわけではありませんが、そういいたいのかなぁと思いました(When to use itの後半)。
  • そういえば、iBatis3はこんな感じで動的クエリを組み立てられるらしいですよ(http://ibatis.apache.org/java.cgi のUSERガイドより抜粋)。Function Sequenceですね。
    private String selectPersonSql() {
        BEGIN(); // Clears ThreadLocal variable
        SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
        SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
        FROM("PERSON P");
        FROM("ACCOUNT A");
        INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
        INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
        WHERE("P.ID = A.ID");
        WHERE("P.FIRST_NAME like ?");
        OR();
        WHERE("P.LAST_NAME like ?");
        GROUP_BY("P.ID");
        HAVING("P.LAST_NAME like ?");
        OR();
        HAVING("P.FIRST_NAME like ?");
        ORDER_BY("P.ID");
        ORDER_BY("P.FULL_NAME");
        return SQL();
    }

みんなの突っ込み

  • なんとなくですが、bare functionを使いたいが、グローバル関数が問題になる、という状況を解決するのがstatic importだといいたいのでは?論理の流れとしては、bare functionが使いたい => グローバル関数にするとまずい => 名前空間を使え => それじゃbare functionじゃない => static importを使え、なのではないかと。particular mechanism to handle thisの「this」がこの状況を反映してるのではないかと思います。言葉足らず過ぎですが。 -- kentaro714? 2009-10-31 (土) 03:02:31
  • なるほど!名前空間を使ってスコープを狭めているんだけどbare functionのように見えるstatic importがすばらしい、という話なんですね。 -- 中村? 2009-10-31 (土) 09:53:31
  • でも、public以外にするとかの手法と組み合わせないとどこからでも参照できてしまうというグローバル関数の問題は解決しないですよねぇ。 -- 中村? 2009-10-31 (土) 10:03:02
  • Object Scopingは次の章のTopicで登場。あわせて読みたい。 -- kakuda? 2009-12-12 (土) 09:14:49