DSL / Nested Closure


DSL

Nested Closure

一言要約

Nested Closure を用いることで、スコープの限定と評価順序の制御ができるようになる。

要約

  • Nested Function、Function Sequence、および、Method Chaining への拡張として、Nested Closureを用いるのは価値がある。
  • クロージャを評価するコードを書かないといけない。
  • Context Variable をセットするなど、評価の前後に他のタスクを行える。
    • 多くの Context Variable がいたる所に現れるという問題を解消できる。
    • Context Variable をクロージャの引数に渡すと、、、
      • Method Chaining の場合:最初の要素として使用できる。
      • Function Sequence の場合:Context Variable が明示的に存在させることで、コードを追いかけるのがより簡単になる。

Nested Function:

processor(
  cores 2,
  i386
)

Function Sequence + Closure: (1)

processor do
  cores 2
  i386
end

Method Chaining + Closure:

processor do |p|
  p.cores(2).i386
end

Function Sequence + Closure: (2)

processor do |p|
  p.cores 2
  p.i386
end
  • 複数のクロージャを渡す
    • Smalltalk の次の例のように、個別に評価することが出来る。
      aRoom
        ifDark: [aLight on] 
        ifLight: [aLight off]
  • ホスト言語によっては、クロージャをサポートしていなかったり、サポートしていたとしても、不恰好なキーワードの羅列のようなDSLになってしまったりする。
  • Nested Function、Function Sequence、Method Chaining それぞれで、クロージャは役に立つが、その利点を突き詰めれば、クロージャの前後で特定のセットアップと終了処理が実行できる、ということに尽きる。

例:Function Sequence + Nested Closure

もともとの Function Sequence

class BasicComputerBuilder < ComputerBuilder
  def doBuild
    computer
      processor 
        cores 2
        i386
        processorSpeed 2.2
      disk
        size 150
      disk 
        size 75
        diskSpeed 7200
        sata
  end
end

Rubyの場合

class BasicComputerBuilder < ComputerBuilder
  def doBuild
    computer do 
      processor do 
        cores 2
        i386
        processorSpeed 2.2
      end
      disk do
        size 150
      end
      disk do
        size 75
        diskSpeed 7200
        sata
      end
    end
  end
end
  • do-end デリミタが加わっている。
    • フォーマット規約という明示的な階層構造を加えていて、読者の観点からは厄介になっているわけではない。
    • (実装略→必要に応じて原典をみる)

C#の場合

class Script : Builder {
  protected override void doBuild() {
    computer(() => {
      processor(() => {
        cores(2);
        i386();
        processorSpeed(2.2);
      });
      disk(() => {
        size(150);
      });
      disk(() => {
        size(75);
        diskSpeed(7200);
        sata();
      });
    });
  }
}
  • 構造は Ruby と同じだが、たくさんの記号の羅列がある。
  • delegate節によって渡されるクロージャの型を定義しなければならない。
  • C#の()=> {...}より、Rubyのdo...endクロージャ区切り文字の方が、より自然。

Method Chaining + Closure

ComputerBuilder.build do |c|
  c.
    processor do |p|
      p.cores(2).
        i386.
        speed(2.2)
    end.
    disk do |d|
      d.size 150
    end.
    disk do |d|
      d.size(75).
        speed(7200).
        sata
    end
end
  • クロージャへの引数にオブジェクトを渡している。その結果、雑音を増やしている。
  • しかし、Object Scopingを必要としせず、その結果、ぶつ切れのスタイルをもつコードを容易に使用できる。
  • (実装略→必要に応じて原典をみる)

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

担当者のつぶやき

  • 途中から、同じことの繰り返しのような気がする。(冗長部分をカットすれば、もっとスッキリする!?)
  • Ruby の instance_eval って何だ?(単に杉野の勉強不足)

みんなの突っ込み