DSL / Closure


DSL

クロージャ

一言要約

  • クロージャは、コードの断片をオブジェクトとして扱い、引数に与えたり、変数に格納できたりする。
  • クロージャは、DSL にとって Nested Closure に必要なだけでなく、Adaptive Modelを定義するのが、より簡単になる場合がある。

要約

  • クロージャは最近注目されてきた。
  • 言語によっては、lambda(lisp)、匿名関数、ブロック(Ruby, Smalltalk)、と呼ばれることもある。
  • クロージャとは「オブジェクトとして扱えるコードの断片」といえる。

例:あるコレクションからのデータの部分集合を得る

  • 従業員のリストから、多頻度旅行者(Heavy Travellers)を取り出す。
    int threshold = ComputeThreshold();
    var heavyTravellers = new List<Employee>();
    foreach (Employee e in employeeList)
      if (e.MilesOfCommute > threshold) heavyTravellers.Add(e);
  • 従業員のリストから、マネージャを取り出す。
    var managerList = new List<Employee>();
    foreach (Employee e in employeeList)
      if (e.IsManager) managerList.Add(e);
  • このコードには重複がある。重複していないところをパラメータ化したい。
  • クラスを用いると、次のようになる。
    class MyList<T> {
      private List<T> contents;
      public MyList(List<T> contents) {
        this.contents = contents;
      }
      public List<T> Select(FilterFunction<T> p) {
        var result = new List<T>();
        foreach (T candidate in contents)
          if (p.Passes(candidate)) result.Add(candidate);
        return result;
      }
    }
    interface FilterFunction<T> {
      Boolean Passes(T arg);
    }
  • これを使うには、次のように出来る。
    var managers = new MyList<Employee>(employeeList).Select(new ManagersPredicate());
    class ManagersPredicate : FilterFunction<Employee> {
      public Boolean Passes(Employee e) {
        return e.IsManager;
      }
    }
  • プログラミング上の充足感があるが、プレディケートクラスのコードが長すぎる。
  • 多頻度旅行者の場合は、さらに、次のようになる。
    var threshold = ComputeThreshold();
    var heavyTravellers = new MyList<Employee>(employeeList).
      Select(new HeavyTravellerPredicate(threshold));
    
    class HeavyTravellerPredicate : FilterFunction<Employee> {
      private int threshold;
      public HeavyTravellerPredicate(int threshold) {
         this.threshold = threshold;
      }
      public Boolean Passes(Employee e) {
        return e.MilesOfCommute > threshold;
      }
    }
  • クロージャを使えば、よりエレガントに解決できる。
  • C# の匿名デリゲータを使うと、次のようになる。
    var threshold = ComputeThreshold();
    var heavyTravellers =  employeeList.FindAll(delegate(Employee e) {
      return e.MilesOfCommute > threshold;
    });
  • メリット
    • 1:コード量が少ない。
    • 2:ライブラリも使いやすくできる。(C#2 では、デリゲートを使って、大幅にライブラリが書き直された。)
    • 3:パラメータが簡単に使用できる。(上の、threshold のように)
      • パラメータはレキシカルスコープに閉じ込められる。
      • デリゲートをどこかに保存したとしても、それらの変数はまだ使える。
      • スタック・フレームのコピーを取る必要がある。(この理論と実装の周辺はかなりトリッキー)
  • C#3 だと、多頻度旅行者は、さらに、次のようなコードにできる。
    var threshold = ComputeThreshold();
    var heavyTravellers = employeeList.FindAll(e => e.MilesOfCommute > threshold);
  • コードがさらに短くなっている。
  • C#3 には型推論能力があるので、デリゲートの例のように型を指定する必要がない。
  • クロージャを変数にとっておいて、あとでそれを使用することも出来る。
    • (本文では、Club の例が C# と Ruby で記述されているが、そんなに重要ではないように思えるので、省略)
  • クロージャはDSLsにおける2、3の役に立つ役割を果たす。
  • 最も明らかには、それらはNested Closureのための必須のエレメント。
  • また、Adaptive Modelを定義するのは、より簡単になる場合がある。

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

  • Club クラスの最初のプログラムで createHeavyTravellersClub?() の中の、new Club(emps, e => e.MilesOfCommute? > threshold); の第一引数 emps は不要なのでは?

担当者のつぶやき

  • C#3 のクロージャは、Ruby のブロックぐらい簡潔でよい感じ。Java にも同じような構文があればよいのに。

みんなの突っ込み