汎用型、デザインパターン、関数型の説明ドキュメント

はじめに
このドキュメントでは汎用型、デザインパターン、関数型を組み合わせた場合の性質を説明します。
このドキュメントの目的
汎用型、デザインパターン、関数型の性質を説明することにより Woolpackへの理解を読者に促します。
Woolpackとは
Java5から導入されたジェネリクスを使用して、 関数型言語の特性をオブジェクト指向言語で表現しているAPIです。
Woolpackの目標
オブジェクト指向と関数型の関連を明らかにし、 再発見した利点をソフトウェア開発に適用します。
インストール
ライブラリを利用するプログラムのクラスパスに追加します。
汎用型の基本性質
Java5から導入された汎用型の性質を、例を交えながら説明します。
汎用型のネーミング
汎用型を意味する概念に複数のネーミングが通用しています。 Webで検索する際は以下の単語を試行してみてください。
ネーミング
汎用型
総称型
generic type
ジェネリクス
generics
型パラメータ
type parameter
総称クラス
ジェネリッククラス
generic class
汎用型を導入する前
インタフェースjava.util.List<E>は順序付けられたコレクションであり、以下のメソッドがあります。
汎用型導入前汎用型導入後機能
boolean add(Object e)boolean add(E e)要素を追加する
Object get(int index)E get(int index)指定された位置の要素を取得する
汎用型を使用しない状態では、取り扱うことができる型をプログラム中で制御する必要があり、代入誤りや取り出し後キャスト誤りの危険性があります。
List list = new ArrayList();
list.add(new Integer(3));// コンパイル警告
list.add(new Double(3.14));// コンパイル警告
Integer i0 = (Integer) list.get(0);// 正常
Integer i1 = (Integer) list.get(1);// 実行時エラー
汎用型を導入した後
汎用型を使用すると、取り扱うことができる型をリスト変数に割り当てることができるため、明示的なキャストが不要になります。
List<Integer> list = new ArrayList<Integer>();
list.add(new Integer(3));
list.add(new Double(3.14));// コンパイルエラー
Integer i0 = list.get(0);// 正常
Double d1 = (Double) list.get(1);// コンパイルエラー
言語に汎用型機構を導入する目的
汎用型は以下の目的のために考えられた概念です。
生産効率を維持するエラーを製造時に検出する
プログラムを再利用する型をパラメータにしてプログラムをテンプレート化し、それを利用したプログラムを製造する時に型を割り当てる
Java汎用型の制限
Javaの汎用型機構では、 型パラメータを付加した変数の値を型パラメータを付加していない変数に代入するコードを、 コンパイルエラーではなくコンパイル警告レベルにしています。 プログラマが型に関する危険性を回避ようにプログラムを実装する必要があります。
List<Integer> list = new ArrayList<Integer>();
List list2 = list;
list2.add(new Double(3.14));// コンパイル警告
Integer i0 = list.get(0);// 実行時エラー
上記の例ではDoubleをIntegerにキャストしようとしてClassCastExceptionが発生します。
複数の型パラメータ
複数の型パラメータを指定するには型パラメータをカンマで区切ります。 例えば、インタフェースjava.util.Map<K, V>はキーを値にマッピングするオブジェクトであり、以下のメソッドがあります。
メソッド機能
V put(K key, V value)キーと値の組を関連付ける
V get(Object key)指定されたキーに関連付けられた値を返す
入れ子の型宣言
型を入れ子にして宣言することができます。 次の例は文字列を文字列の一覧にマッピングしたMapです。
Map<String, List<String>> listMap = new HashMap<String, List<String>>();
listMap.put("key0", new ArrayList<String>());
List<String> list = listMap.get("key0");
境界ワイルドカード
型変数に境界を指定することができます。
視点\表記? extends E? super E
単語上限境界下限境界
型の集合Eの子である型の集まりEの親である型の集まり
変数(引数、返却値)型Eの変数に設定することができる型の集まり型Eの値を設定することができる変数の型の集まり
境界ワイルドカード「?」は「? extends Object」の略です。
上限境界ワイルドカードの動作
インタフェースjava.util.List<E>に以下のメソッドがあります。
メソッド機能
boolean addAll(Collection<? extends E> c)指定されたコレクションの各要素をこのオブジェクトに追加する
上限境界ワイルドカードを使用して以下の静的型検証をコンパイラに行わせることができます。
List<SQLException> list0 = new ArrayList<SQLException>();
List<SQLWarning> list1 = new ArrayList<SQLWarning>();
list0.addAll(list1);// 正常
list1.addAll(list0);// コンパイルエラー
java.sql.SQLWarningはjava.sql.SQLExceptionを継承したクラスです。 SQLExceptionではあるがSQLWarningでないインスタンスが格納されているかもしれないlist0をlist1に追加することができないことがコンパイラでチェックされています。
下限境界ワイルドカードの動作
クラスjava.util.Collectionsに以下のメソッドがあります。
メソッド機能
static <T> boolean addAll(Collection<? super T> c, T... elements)指定されたコレクションに指定された全ての要素を追加する
下限境界ワイルドカードを使用して以下の静的型検証をコンパイラに行わせることができます。
List<SQLException> list0 = new ArrayList<SQLException>();
Collections.addAll(list0, new SQLWarning());// 正常
Collections.addAll(list0, new Exception());// コンパイルエラー
非共変性
(http://www-06.ibm.com/jp/developerworks/java/050218/j_j-jtp01255.htmlにも説明がありますがここでもかいつまんで説明します。)
Javaの配列は共変(Covariant)ですが、汎用型は型安全性を維持するため共変ではありません。
Integer[] array0 = new Integer[3];
Number[] array1 = array0;// 正常
array1[0] = 3.14;// 実行時エラー

List<Integer> list0 = new ArrayList<Integer>();

List<? extends Number> list1 = list0;// 正常
list1.add(Double.valueOf(3.14));// コンパイルエラー

List<Number> list2 = list0;// コンパイルエラー

List<? super Integer> list3 = list0;// 正常
list3.add(Integer.valueOf(3));// 正常
配列では配列代入時にエラーにならずに値代入時に実行エラーになります。 汎用型ではリスト代入時にエラーを出すことにより値代入の整合性を確保します。 共変のように変数を扱うには境界ワイルドカードを使用する必要があります。 Javaでは配列と汎用型の動作が異なるため、配列の汎用型の組み合わせに以下の例のような制限があります。
List<String[]> arrayList = null;
arrayList = new ArrayList<String[]>();// 正常

List<?>[] listArray0 = null;// 正常
listArray0 = new List<?>[2];// 正常

List<String>[] listArray1 = null;// 正常
listArray1 = new List<String>[2];// コンパイルエラー
関数の抽象化
説明を継続するため、関数(IO、入出力)を抽象化した以下のインタフェースを導入します。
public interface Fn<C, R, E extends Exception> {
  R exec(C c) throws E;
}
以下の意味があります。
機能型パラメータ意味
引数、入力C型Cの値を受け取る関数であり
返却値、出力R型Rの値を返す関数であり
例外の出力E型Eの例外を発生させる可能性がある関数である
Woolpackはこのインタフェースがベースになっています。 またこのインタフェースはFunctionデザインパターンに従っています。 Functionデザインパターンは「Java Generics and Collections」 で紹介されています。
委譲と汎用型
委譲(Delegation)に汎用型を導入することにより、型に関して最大限にテンプレート化された部品を定義することができます。 型宣言「Fn<? super C, ? extends R, ? extends E>」は委譲元からみると次の意味があります。
機能型パラメータ意味
引数? super C型Cの値を渡すことができる委譲先であり
返却値? extends R型Rの変数に格納することができる型を返してくれる委譲先であり
例外? extends E型Eの例外として投げることができる例外だけを発生させる可能性がある委譲先である
関数の合成は以下のように定義されるべきです。
public class JoinFn<A, B, C, E extends Exception> implements Fn<A, C, E> {
  private Fn<? super A, ? extends B, ? extends E> firstFn;
  private Fn<? super B, ? extends C, ? extends E> secondFn;
  public C exec(final A a) throws E {
    return secondFn.exec(firstFn.exec(a));
  }
  // コンストラクタとアクセサは省略
}
なぜなら以下の各値はそれぞれ次の意味を持つからです。
記号説明
Fn<? super A,型Aの値を受け取ることができ
? extends B,型Bの変数に設定することができ
? extends E>型Eの例外として投げることができる関数と
Fn<? super B,型Bの値を受け取ることができ
? extends C,型Cの変数に設定することができ
? extends E>型Eの例外として投げることができる関数を合成すると
Fn<A,型Aの値を受け取り
C,型Cの値を返し
E>型Eの例外を発生させる可能性がある関数になるから
数学の用語を使用すると次のように説明することができます。 委譲と汎用型の組に関する性質は以下のように集約することができます。 上記の性質は「The Get and Put Principle」と呼ばれています。
型に依存しないクラスを定義する
型によって処理が変化しない場合、型をパラメータ化した実装を定義することができます。 次の例は入力をそのまま出力するように実装したクラスです。
public class EchoFn<C, E extends Exception> implements Fn<C, C, E> {
  public C exec(final C c) {
    return c;
  }
}
型が確定したクラスを定義する
インタフェースに型パラメータが指定されている場合でも、型を確定して実装したクラスを定義することができます。 次の例は入力をStringに、出力をBooleanに確定して、文字列の最大サイズを検証するように実装したクラスです。
public class MaxLengthChecker<E extends Exception> implements Fn<String, Boolean, E> {
  private int value;
  public Boolean exec(final String c) {
    return c.length() <= value;
  }
  // コンストラクタ、アクセサは省略
}
型パラメータを制限した型を定義する
関数を抽象化したインタフェース(再掲)の型Eは、Exceptionとそれを継承した型だけを割り当てることができるように制限しています。
public interface Fn<C, R, E extends Exception> {
  R exec(C c) throws E;
}
性質が付加された型パラメータを定義する
インタフェースによって性質が付加された型パラメータを宣言することにより、そのインタフェースのメソッドだけを利用するクラスを定義することができます。 インタフェースjava.lang.Comparable<T>はオブジェクトが順序付けられていることを表し、以下のメソッドがあります。
メソッド機能
int compareTo(T o)このオブジェクトと指定されたオブジェクトの順序を比較する
次の例はComparableが実装された型を受け取って、最大値を検証するように実装したクラスです。
public class MaxChecker<C extends Comparable<C>, E extends Exception> implements Fn<C, Boolean, E> {
  private C value;
  public Boolean exec(final C c) {
    return value.compareTo(c) >= 0;
  }
  // コンストラクタ、アクセサは省略
}
Comparableが実装されている型であれば型に依存せずに最大値を検証することができます。
複数の性質を持つ型パラメータを定義する
複数の性質を持つ型パラメータを指定するには「extends」と「&」を使用します。 例のために次のインタフェースを導入します(いずれもWoolpackで宣言したインタフェースです)。
インタフェース機能プロパティ
NodeContextDOMノードの変数を保持するコンテキストorg.w3c.dom.Node node
IdContext識別子である変数を保持するコンテキストString id
次の例はインタフェースとを実装した型を引数にとってHTMLのIFrameをHTMLのtableに変換するクラスです。
public class IFrameInserter<C extends NodeContext & IdContext, E extends Exception> implements Fn<C, Void, E> {
  public Void exec(final C c) throws E {
    // 省略
  }
  // コンストラクタ、変数、アクセサは省略
}
型パラメータCの実装に依存せずに複数の性質をもつ型パラメータに対して実装することができます。 「&」の右にはインタフェースだけを宣言することができます。
疑似typedefアンチパターン
(http://www-06.ibm.com/jp/developerworks/java/060310/j_j-jtp02216.shtmlにも説明がありますがここでもかいつまんで説明します。)
Java言語には型に短縮名を定義するための「typedef」機能が存在しないので、型宣言のための記述量が増加する傾向があります。 typedefの代わりに継承を使用してしまうと再利用性を制限してしまうことが「疑似typedefアンチパターン」の趣旨です。
次のように型パラメータが確定したコレクションクラスを用意してみます。
class StringList extends ArrayList<String>{}// 悪い例
完全なtypedefを実現するわけではないので、StringListとList<String>には以下の代入関係があります。
List<String> list0 = new StringList();// 正常
StringList list1 = new ArrayList<String>();// コンパイルエラー
StringListを受け取る関数を定義してしまった場合、その関数にはもはやList<String>のインスタンスを渡すことができません。
public void doSomething(StringList list){
  // ...
}
複数の性質を持つ型パラメータを定義する際は、受け皿となるクラスあるいはインタフェースを用意する代わりに「&」を用いて宣言することにより再利用性を維持することができます。次のインタフェース宣言は悪い例です。
public interface MyIdNodeContext extends IdContext, NodeContext {}// 悪い例
型推論
Javaでは 型パラメータが付加されたインスタンスをスタティックメソッドで生成することにより、インスタンス生成における型パラメータを略記することができます。 まずスタティックメソッドによるファクトリメソッドを用意します。
public final class FnUtils {
  public static <C> Fn<C, C, RuntimeException> echo() {
    return new EchoFn<C, RuntimeException>();
  }
  // ...
}
インスタンスの生成を以下の様に略記することができます。
Fn<String, String, RuntimeException> fn0 = new EchoFn<String, RuntimeException>();// before
Fn<String, String, RuntimeException> fn1 = FnUtils.echo();// after
さらにスタティックインポートを使うと以下の様に略記することができます。
import static woolpack.fn.FnUtils.*;
// ...
Fn<String, String, RuntimeException> fn1 = echo();
汎用型とデザインパターンと関数型
汎用型とデザインパターンと関数型を組み合わせた場合の性質を、例を交えながら説明します。
用語の定義
以下の説明において、次の用語を使用します。
用語意味
関数インタフェース関数を抽象化したインタフェース(再掲)
public interface Fn<C, R, E extends Exception> {
  R exec(C c) throws E;
}
関数クラス関数インタフェースの実装
関数ファクトリ関数クラスを生成する型推論のためのスタティックメソッド
関数オブジェクト関数クラスのインスタンスを組み立てたオブジェクト
これらの用語はこのドキュメントのみで使用されます。
汎用型と関数型でデザインパターンを考える意義
関数インタフェースはインタフェースに定義されたメソッドがひとつだけであり、 また入力と出力が汎用型により抽象化されているため、 デザインパターンの特性が見やすい形で表れるからです。
Null Object
Null Objectは「何もしない委譲先」の概念を一般化したパターンですが、 関数型の場合、何もせずとも何かしらの値を返す必要があります。 以下の性質をもつ関数オブジェクトはNull Objectとみなせます。
関数ファクトリ生成される関数オブジェクトの性質
FnUtils#fix()固定値を返す
FnUtils#echo()引数をそのまま返す
関数を合成する際、これらの関数はターミナル的な役割を持つことができます。
汎用型と関数型を組み合わせているので、型のバリエーションに対してひとつの関数クラスで対処することができます。 例えばStringのecho、Integerのechoをひとつの関数ファクトリで生成することができます。
Fn<Integer, Integer, RuntimeException> fn0 = FnUtils.echo();
Fn<String, String, RuntimeException> fn1 = FnUtils.echo();
Delegation
委譲は以下の目的のために考えられた概念です。
生産効率を維持する
プログラムを再利用する各プログラムの責務(Responsibility)を適切に割り当てる
各プログラムを部品化して組み立てるプログラム同士の接続を実装に依存しないようにする
プログラム同士の接続を形式(インタフェース)に依存するようにする
前述の関数クラス「JoinFn」は、合成されるふたつの関数について知らなくても、 型の関係がマッチしているならば関数の合成を定義することができています。 これは「JoinFn」を含むデザインの系が関数インタフェースに依存するように設計されているからです。
処理定義のフェーズ分離
委譲の概念、特にFunctionパターンを導入したプログラムでは、 処理部品を定義するフェーズと処理部品を組み立てるフェーズ、そして処理部品を実行するフェーズを分離するところに特徴があります。
処理部品を定義するフェーズでは以下の例のように部品を定義していきます。
public class RegExpConverter<E extends Exception> implements Fn<String, String, E> {
  private Pattern pattern;
  private String replacement;
  public String exec(final String c) {
    return pattern.matcher(c).replaceAll(replacement);
  }
}

処理部品を組み立てるフェーズでは以下の例のように部品を組み立てていきます。
final Fn<String, String, RuntimeException> fn = new RegExpConverter<RuntimeException>(Pattern.compile("##(.+)##"), "$1");
処理部品に対し依存性が自然に注入されます(依存性の注入)。 またオブジェクトを生成する処理が処理部品の組み立てるフェーズに集中することにより 処理部品と組み立て処理の間で依存性が自然に逆転します(制御の逆転)。 既存のDI/IoCコンテナの代替として使用することができます。
処理部品を実行するフェーズでは以下の例のように処理を実行します。
final String result = fn.exec("mamo##hona##mimo");
assertEquals("mamohonamimo", result);
RegExpConverterクラスやPatternクラス、 変換元の正規表現や変換後の置き換えパターン「"$1"」に依存せずに、 Fnインタフェースだけに依存する処理ブロックやクラスを設計することができます。 それによって疎結合を維持することができます。
処理定義の階層化
以下の図は、処理定義の階層化を表したものです。
図:処理定義の階層化
layerアプローチ説明
part処理部品を定義する層ボトムアップ処理の詳細を部品化する
composition処理部品を組み立てる層トップダウン部品化された処理を組み立てる
execution処理を実行する層トップダウン組み立てられた処理を実行する
委譲の概念、特にFunctionパターンを導入したプログラムでは、 処理部品を定義する層・組み立てる層・実行する層に階層化することにより ボトムアップとトップダウンが自然に結合されます。
委譲するだけのクラスの導入
説明を継続するために、委譲するだけの次のクラスを導入します。
public class Delegator<C, R, E extends Exception> implements Fn<C, R, E> {
  private Fn<? super C, ? extends R, ? extends E> fn;
  public R exec(final C c) throws E {
    return fn.exec(c);
  }
  // コンストラクタ、アクセサは省略
}
このクラスは次の目的を持っています。
  1. 継承したクラスでアクセサ宣言を省略する。
  2. 初期化が多層になっている場合、「委譲することの宣言」と「委譲する関数オブジェクトの割り当て」のタイミングを分離する。
Abstract Factory
Abstract Factoryはオブジェクトの生成処理を取り替えられるように設計したパターンです。 次のクラスはjava.text.Formatのクローンを生成して返却する関数クラスです。
public class CloneFormatFactory<E extends Exception> implements Fn<Object, Format, E> {
  private Format format;
  public Format exec(final Object c) {
    return (Format) format.clone();
  }
  // コンストラクタ、アクセサは省略
}
Formatのクローンを生成するファクトリとみなせます。
Strategy
Strategyは、処理を取り替えられるように設計したパターンです。 次のクラスはjava.text.Formatを使用してオブジェクトを文字列に変換する関数クラスです。
public class FormatConverter<E extends Exception> implements Fn<Object, String, E> {
  private Fn<?, ? extends Format, ? extends E> formatFactory;
  public String exec(final Object c) throws E {
    return formatFactory.exec(null).format(c);
  }
  // コンストラクタ、アクセサは省略
}
フォーマットのファクトリが取り替えられるようになっています。 例えばスレッドローカルから取得したロケールによって、生成するFormatを分岐するファクトリを設定することもできます。 「取り替えられる」という性質がStrategyの特徴です。
Currying
Currying(カリー化)は関数の引数を部分的に適用した関数を生成することです(関数の部分適用と言います)。 関数インタフェースはカリー化を行うように設計していませんが、論理的にカリー化された関数クラスを設計することができます。 例えば前に説明した「MaxLengthChecker」は以下のようにカリー化されています。
カリー化意味
String#length() <= maxLength文字列と閾値を引数にして真偽値を返す文
BoolUtils#checkMaxLength()閾値を引数にして「文字列を引数にして真偽値を返す関数クラス」を生成する関数ファクトリ
オブジェクト指向におけるカリー化は「変数が確定するタイミングのズレをオブジェクトのファクトリに対応させる」パターンとみなせます。 また「処理定義のフェーズ分離」の説明は「処理のカリー化」とみなせます。
型のCurrying
Javaの汎用型機構では型パラメータの一部を定義時に型付けることができます。 以下は例外型パラメータだけ確定し、引数と返却値の型パラメータを確定していない例です。
public final class FnUtils {
  public static <C, R> FixFn<C, R, RuntimeException> fix(final R value) {
    return new FixFn<C, R, RuntimeException>(value);
  }
  // ...
}
型を確定するタイミングを分散させることができるので「型のカリー化」とみなせます。
Chain of Responsibility
Chain of Responsibilityは、 if句とthen句をひとつのクラスにまとめ、else句の箇所を委譲するように設計したパターンです。 以下のソースのようなコードになります。
public class ToArrayConverter extends Delegator<ConvertContext, Void, RuntimeException> {
  @Override
  public Void exec(final ConvertContext c) {
    if (!c.getToType().isArray()) {
      super.exec(c);
      return null;
    }
    // convert to array
    return null;
  }
  // コンストラクタは省略
}
Builder
Builderは、オブジェクトの組み立て処理をカプセル化するように設計したパターンです。 型推論とBuilderパターンを使用して型パラメータ付きのコレクションを略記で組み立てることができます。 次のクラスはビルド用メソッドを追加したArrayList拡張です。
public class BuildableArrayList<V> extends ArrayList<V> {
  public BuildableArrayList<V> list(final V v) {
    add(v);
    return this;
  }
  // ...
}
次のクラスは型推論を適用するためにスタティックなファクトリメソッドです。
public final class Utils {
  public static <V> BuildableArrayList<V> list(final V v) {
    return new BuildableArrayList<V>().list(v);
  }
  // ...
}
次のようにして使います。
List<String> list = Utils.list("key0").list("key1");
java.util.Arraysクラスには配列を使用したListファクトリメソッドがあります。
List<String> list = Arrays.asList("key0", "key1");
Javaでは汎用型付きの配列を宣言することはできないので、汎用型が付けられたコレクションを生成するときにBuilderパターンを使用します。
親の型としてリストを生成したい場合は、はじめのメソッドだけ型情報を教える必要があります。次の二通りの方法があります。
List<Number> list0 = Utils.list((Number) 3).list(3.14);
List<Number> list1 = Utils.<Number>list(3).list(3.14);
高階関数
高階関数(higher-order function、汎関数、functional)は関数を引数または返却値にする関数のことです。
次の例は特定の高階関数を実行する関数クラスです。
public class ExecFn<C, R, E extends Exception> implements Fn<C, R, E> {
  private Fn<? super C, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E> fn;
  public R exec(final C c) throws E {
    return fn.exec(c).exec(c);
  }
  // コンストラクタ、アクセサは省略
}
委譲先の型宣言 「Fn<? super C, ? extends Fn<? super C, ? extends R, ? extends E>, ? extends E>」 で、ふたつの層の関数の引数が同一(? super C)になっているところが特徴的です。 ExecFnとSwitchFn(Map#get()の関数クラスによるアダプタ)を利用して オブジェクトに対するswitch句を定義することができます。
関数ファクトリはスタティックメソッドなので可用性が制限されています。 それを回避するために、 例えば関数インタフェースを使用して関数の合成(前述のJoinFn)を定義することも可能です。 しかし言語仕様の特性のため機能のわりに記述が多くなります。
Interpreter
Interpreterは、Context 役のインスタンスに Expression 役のCompositeツリーの中をくぐらせて Context 役の内部状態を加工するパターンです。
図:インタープリタ デザインパターンの構造

次の例は正規表現で2度変換する関数オブジェクトを組み立てています。
final Fn<String, String, RuntimeException> fn = FnUtils.join(
  ConvertUtils.convertRegExp(Pattern.compile("#@@([^@]+)@#"), "?$1?"),
  ConvertUtils.convertRegExp(Pattern.compile("#@([^@]+)@#"), "!$1!")
);
関数型言語の普通の関数の合成は、Context役がImmutable(不変)であるInterpreterとみなせます。
次の例はXPathで該当したノードを削除する関数オブジェクトを組み立てています。
final Fn<NodeContext, Void, RuntimeException> fn = XmlUtils.findNode(JXPUtils.list("//*[@id=\"dummy\" or @name=\"dummy\"]"), XmlUtils.REMOVE_THIS);
NodeContext型の引数に対して意図的に副作用を発生させています。 関数型言語の副作用がある関数の合成は、Context役がMonad(Haskellの場合)であり、 かつContext役にPrototypeの性質がないInterpreterとみなせます。
Interpreterのメタファ
デザインパターンの各解説書ではインタープリタ デザインパターンのメタファとして「文法表現」を与えていますが、 ここではより現実的な実態として「組立式コンベア」をメタファとして与えます。 インタープリタ デザインパターンのメタファと値チェックによる例を以下に示します。
概念メタファ例の図との対応
インタープリタ デザインパターン加工対象を加工するための組立式の生産ライン/流れ作業/コンベア例のクラス全体
Expression 役コンベア部品の仕様CheckExpression インタフェース
Context 役コンベアに乗って流れてくる、加工対象を入れるトレーに注文書を付加したものValuesContext クラス
Expression 役の interpret メソッドコンベア同士をつなぐための接続点/ジョイント(受動側)CheckExpression インタフェースの interpret メソッド
Expression 役の各実装加工対象を加工する機能を付加したコンベアSerialCheckExpression / RequiredCheckExpression / MinCheckExpression / MaxCheckExpression の各クラス
Expression 役の各実装に定義された委譲先の Expression 役の属性値コンベア同士をつなぐための接続点/ジョイント(能動側)SerialExpression クラスの list 属性
Expression 役の各実装に定義された Expression 役以外のオブジェクト属性値機能を付加したコンベアの操作レバーMinCheckExpression / MaxCheckExpression クラスの value 属性
図:インタープリタ デザインパターンのクラス例(説明用)
図:インタープリタ デザインパターンのコラボレーション例(説明用)
「モナドの物理的なアナロジー」(http://www.sampou.org/haskell/a-a-monads/html/analogy.html)では 「機械化された組み立てライン」をアナロジーとして導入しています。 関数型言語とデザインパターンの関連性がメタファによって発現しています。
MVCのメタファ
MVCは業務アプリケーションの責務をM(Model)、V(View)、C(Controller)に分割するパターンです。 MVCにインタープリタ デザインパターンを適用した場合、 「パソコン生産ライン組み立てキット」をメタファとして与えることができます。 以下に概念とメタファの対応を示します。
概念メタファ例の図との対応
Woolpack APIオーダーメードパソコンを製造するラインを組み立てるキットWoolpack API
Woolpack を使用したWebアプリケーションオーダーメードパソコンを製造する工場とその営業所SampleServlet クラス
Servlet コンテナ営業所ServletContainer
web.xml営業所と工場を行き来するトラックの経路情報web.xml
コンストラクタツリーパソコンの製造するための組み立て完了した工場serial 変数を根とするツリー
HTTP リクエストの URL注文書に記載されたパソコンの製品番号変換値が EEContext クラスの id 属性に設定される
HTTP リクエストのパラメータ注文書に記載されたオプション品の明細変換したオブジェクトが EEContext クラスの inputMap 属性に設定される
Context 役のコンテナ定義に記載されたコンポーネント定義パソコンの各製品の基本仕様model 変数
HTML ファイルパソコンの筐体id0.html, id1.html
HTTP レスポンス組み立て完了したパソコンEEContext クラスの node 属性
図:MVC のコラボレーション例(説明用)
Decorator
Decoratorは引数を変換して委譲し、返却値を変換して返すパターンです。 次のクラスは委譲先の結果の否定を返す関数クラスです。
public class NotDelegator<C, E extends Exception> extends Delegator<C, Boolean, E> {
  @Override
  public Boolean exec(final C c) throws E {
    return !super.exec(c);
  }
  // コンストラクタ、アクセサは省略
}
汎用型と関数型を組み合わせているので、型のバリエーションに対してひとつの関数クラスで対処することができます。
Proxy
Proxyは委譲を制御するパターンです。
次のクラスは委譲先で生成したオブジェクトをキャッシュする関数クラスです。
public class MapCache<C, K, E extends Exception> implements Fn<C, Object, E> {
  private Fn<? super C, ? extends Map<? super K, Object>, ? extends E> mapGetter;
  private Fn<? super C, ? extends K, ? extends E> keyGetter;
  private Fn<? super C, ?, ? extends E> maker;
  public Object exec(final C c) throws E {
    final Map<? super K, Object> map = mapGetter.exec(c);
    final K key = keyGetter.exec(c);
    Object value = map.get(key);
    if (value == null) {
      value = maker.exec(c);
      map.put(key, value);
      return value;
    }
    return value;
  }
  // コンストラクタ、アクセサは省略
}
汎用型と関数型を組み合わせているので、型のバリエーションに対してひとつの関数クラスで対処することができます。
DIのネーミング
汎用型を意味する概念に複数のネーミングが通用しています。 Webで検索する際は以下の単語を試行してみてください。
ネーミング
依存性の注入
Dependency injection
DI
制御の逆転
Inversion of Control
IoC
DI/AOPコンテナ
DI/AOPコンテナと汎用型+デザインパターン+関数型について、以下の対応関係が与えられます。
性質DI/AOPコンテナ汎用型+デザインパターン+関数型
DI/AOPを宣言する場所 規約による抽象化、XML、初期化処理を行う場所(コンストラクタなど) 関数ファクトリ、初期化処理を行う場所(コンストラクタなど)
DI/AOPを行うタイミング プログラム実行直後の初期化フェーズ
DIの実現方式 DIコンテナによる隠蔽、バイトコード加工技術、リフレクション、ビルダーパターン デザインパターン(主にInterpreter、Strategy、Currying)
処理のインターセプト 主にバイトコード加工技術によるAOP Proxy、Decorator、Abstract Factory
汎用型+デザインパターン+関数型の組み合わせにおいて、DI/AOPコンテナはコンパイラと関数ファクトリに組み入れられているとみなせます。 また関数型言語ではDI/AOPコンテナはインタプリタに内蔵されている、またはコンパイラで実行ファイルに組み入れられるとみなせます。
Iterator
Iteratorはオブジェクトを反復して取得する処理を取り替えられるように設計したパターンです。 インタフェースjava.util.Iterator<E>はオブジェクトから要素をひとつずつ取り出す反復子であり、次のメソッドがあります。
メソッド機能
boolean hasNext()次の要素が存在する場合だけtrueを返す
E next()次の要素を返す
void remove()現在の位置の要素を削除する
インタフェースjava.lang.Iterable<T>はIteratorを生成することができるオブジェクトを表し、次のメソッドがあります。
メソッド機能
Iterator<T> iterator()このオブジェクトの反復子を生成する
Iterableを実装したクラスはforeach文を利用することができます。 ListはIterableが実装されているので次の使い方が可能です。
List<Integer> list = new ArrayList<Integer>();
for(Integer i : list) {
  System.out.println(i);
}
配列もIterableが実装されています。
ラムダ計算
ラムダ計算は処理定義の階層化と次のように対応付けることができます。
ラムダ計算処理定義の階層化
ラムダ抽象処理部品を組み立てる層
関数適用処理を実行する層
ラムダ計算の概念とオブジェクト指向の対応を次に示します (http://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%A0%E3%83%80%E8%A8%88%E7%AE%97からの推論)。
ラムダ計算の用語意味オブジェクト指向による説明
アルファ変換束縛変数の名前は重要ではない言語のカプセル化機構で表現されている
ベータ簡約関数の適用関数インタフェースの実行メソッド(applyまたはexec)の実行するとこと対応する
イータ変換関数の外延性関数の引数が副作用を許容する場合は局所的には成立しない
ラムダ式(名前のない関数の記法)をJavaの匿名クラスに対応付けることができます。 次の例では匿名クラスを使用してjava.util.Mapのインスタンスを生成するファクトリのインスタンスを定義しています。
public final class FactoryUtils {
  public static final Fn<Object, Map<String, Object>, RuntimeException> LINKED_MAP_FACTORY = new Fn<Object, Map<String, Object>, RuntimeException>() {
    public Map<String, Object> exec(final Object c) {
      return new LinkedHashMap<String, Object>();
    }
  };
  // ...
}
Closure
ある関数内で引数などによって束縛されていない変数が使用されていた場合、 関数を定義した位置から変数の束縛(代入)を探す機能(性質)のことをクロージャ(Closure)(レキシカルスコープ)(lexical scope)と言います。 Javaでは例えば匿名クラスを使用して関数を生成する関数を定義する際に出現します。 次の例はXPath文からノードを検索するJXPathのアダプタです。
public final class JXPUtils {
  public static Fn<Object, Node, RuntimeException> one(final String expression) {
    JXPathContext.compile(expression);
    return new Fn<Object, Node, RuntimeException>() {
      public Node exec(final Object c) throws RuntimeException {
        return (Node) JXPathContext.newContext(c).selectSingleNode(expression);
      }
    };
  }
  // ...
}
XPath文であるexpression変数を使用して匿名の関数クラスが定義されています。 Javaでは匿名クラスからそれが続するメソッドの変数を参照する場合、finalが宣言されている必要があります。
Before-After
途中の処理の結果にかかわらず後始末処理を行う場合、Javaでは予約語try-finallyを使用することができます。 try-finally構文と同じ目的を表すパターンをBefore-Afterと呼びます。 ところがプログラマが後始末処理を忘れた場合、コンパイラが警告を出すような言語上の仕掛けは現在のところ用意されていません。 そのようなミスを回避するためにProxyパターンを使用することができます。 次の例は委譲先からクエリを取得し、引数をPreparedStatementに設定して PreparedStatement.execute()を実行し、返却値の生成を委譲する関数クラスです。
public class SingleInput<C, R> implements Fn<C, R, Exception> {
  private DataSource dataSource;
  private Fn<? super C, ? extends String, ? extends Exception> queryFactory;
  private Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter;
  private Fn<? super SQLException, ?, ? extends Exception> errorFn;
  public R exec(final C c) throws Exception {
    SQLException e0 = null;
    try {
      final Connection connection = dataSource.getConnection();
      try {
        final PreparedStatement statement = connection.prepareStatement(queryFactory.exec(c));
        try {
          statement.setObject(1, c);
          statement.execute();
          return converter.exec(statement);
        } catch (final SQLException e1) {
          errorFn.exec(e1);
          e0 = e1;
        } finally {
          statement.close();
        }
      } catch (final SQLException e1) {
        errorFn.exec(e1);
        if (e0 == null) {
          e0 = e1;
        }
      } finally {
        connection.close();
      }
    } catch (final SQLException e1) {
      errorFn.exec(e1);
      if (e0 == null) {
        e0 = e1;
      }
    }
    if (e0 != null) {
      throw e0;
    } else {
      return null;
    }
  }
}
PreparedStatementを受け取った委譲先(converter)は、 ConnectionやPreparedStatementの後始末処理を委譲元に任せて ResultSetをフェッチしたり更新件数を抽出したりできます。 また、SingleInputクラスが委譲先(converter)の返却値の型パラメータ「R」をそのまま呼出し元に伝達しているので、 委譲先(converter)の型のバリエーションに対してひとつのクラスで対応することができます。
Double Dispatch
Double Dispatchは自分自身を引数として渡すように設計したパターンです。 次のクラスは関数インタフェースをプロパティとして持つコンテキスト(オブジェクト変換のためのコンテキスト)です。
public class ConvertContext {
  private Object value;
  private Class toType;
  private String propertyName;
  private Fn<ConvertContext, Void, RuntimeException> fn;
  // アクセサは省略
}
次のクラスは自分自身をコンテキストに登録している関数クラスです。
public class SettingFnConverter extends Delegator<ConvertContext, Void, RuntimeException> {
  @Override
  public Void exec(final ConvertContext c) {
    c.setFn(this);
    super.exec(c);
    return null;
  }
  // コンストラクタは省略
}
次のクラスはコンテキストに設定された関数オブジェクトを取り出して委譲する関数クラスの例(配列への変換器)です。
public class ToArrayConverter extends Delegator<ConvertContext, Void, RuntimeException> {
  @Override
  public Void exec(final ConvertContext c) {
    // ...
    final Collection<?> collection = Utils.toCollection(c.getValue());
    // ...
      for (final Object before : collection) {
        c.setValue(before);
        c.getFn().exec(c);
        // ...
      }
    // ...
    return null;
  }
  // コンストラクタは省略
}
Common Lisp ではlabelsを使用して再帰を表現する方法もあるため関数オブジェクトへの参照手段は異なりますが、 Double Dispatchは関数型言語における再帰呼び出しとみなせます。
参考文献
  1. 「Java Generics and Collections」 Maurice Naftalin & Philip Wadler 著 O'Reilly刊 2006 ISBN-10:0-596-52775-6
  2. http://www-06.ibm.com/jp/developerworks/java/050218/j_j-jtp01255.html
  3. http://www-06.ibm.com/jp/developerworks/java/060310/j_j-jtp02216.shtml
  4. http://www.sato.kuis.kyoto-u.ac.jp/~igarashi/papers/pdf/JSSST-tutorial06.pdf
  5. http://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%A0%E3%83%80%E8%A8%88%E7%AE%97
  6. http://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0
  7. http://user.ecc.u-tokyo.ac.jp/~t50473/onlispjhtml/index.html
  8. http://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3
  9. http://www.sampou.org/haskell/a-a-monads/html/analogy.html
履歴
  1. 20070407 新規作成。
  2. 20070408 参考文献、非共変性、Iterableを追加。
  3. 20070408 ラムダ計算、「処理のカリー化」「型のカリー化」に関する言及を追加。
  4. 20070414 他のドキュメントと統合。型のカリー化の説明を変更。クロージャを追加。
  5. 20070414 Before-Afterを追加。
  6. 20070422 誤記修正。「処理定義のフェーズ分離」の説明変更。「ボトムアップとトップダウンの結合」を追加。「非共変性」に説明追加。
  7. 20070425 誤記修正(IterableからIterator)。
  8. 20070428 誤記修正。
  9. 20070503 Double Dispatchを追加。
  10. 20070504 Double Dispatchに補足説明を追加(labels)。ラムダ計算に処理階層との関連を追加。
  11. 20070506 汎用型のネーミングに「総称型」を追加。
  12. 20070602 型推論にスタティックインポートの説明を追加。
トップに戻る
Copyright (C) 2006-2007 Takahiro Nakamura. All rights reserved.