Javaで複合モナド

実験的にテンプレートエンジンを書き始めたのはいいけど、早くもかなりpurityが失われている感じなので整理したい。

実装をざっと見て、

  • テンプレートのモナド
  • コンテキスト(ステートモナド)
  • (あと多分リストも)

が混ざった感じの実装になっているので綺麗に書き直したい。書き直したいがどうにも経験値不足なせいでやり方を思い付かない。そこで経験値稼ぎの為に「ベックの法則と複合モナド - 檜山正幸のキマイラ飼育記 (はてなBlog)」にある複合モナドの例をJavaで実装してみた。


全体の流れとしては、

  1. Listモナドの実装。
  2. Enumモナドの実装。
  3. List>からEnum>を作る関数combinationsの実装。
  4. 複合モナド EnumOfList の実装

という風なっている。それぞれがどういうものかは上記のサイトで見て下さい。

Listモナドの実装

java.util.Listと名前が被るけど気にせずListというクラス名にするよ。
java.util.Listで要素を渡して生成する。

package sample.compositemonad;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import com.google.common.base.Function;

public class List<T> {
    private final java.util.List<T> components;

    public List(java.util.List<T> components) {
        if (components == null) {
            throw new IllegalArgumentException("the arg should not be null");
        }
        this.components = Collections.unmodifiableList(components);
    }
/*

サンプルデータが作りやすいようにコンストラクタを足しておく。

 */
    public List(T... components) {
        this(Arrays.asList(components));
    }
/*

equals/hashCode/toStringを適当に実装(Apache Commons Lang使用)

 */
    public final boolean equals(Object other) {
        return EqualsBuilder.reflectionEquals(this, other);
    }
    public final int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        Iterator<T> it = components.iterator();
        while (it.hasNext()) {
            sb.append(it.next().toString());
            if (it.hasNext()) sb.append(',').append(' ');
        }
        sb.append(']');
        return sb.toString();
    }
/*

あとListといえば、というあたりのメソッドを実装。

 */
    // 要素が空かどうかを判定
    public boolean isEmpty() {
        return components.isEmpty();
    }
    // 先頭の要素を戻す
    public T head() {
        if (isEmpty()) {
            throw new IllegalStateException("empty list has no head.");
        }
        return components.get(0);
    }
    // 先頭の要素を取り除いた残りをListで戻す
    public List<T> tail() {
        if (isEmpty()) {
            throw new IllegalStateException("empty list has no tail.");
        }
        return new List<T>(components.subList(1, components.size()));
    }
    // 末尾にxを付け加えたListを戻す
    public List<T> appended(T x) {
        ArrayList<T> results = new ArrayList<T>(components);
        results.add(x);
        return new List<T>(results);
    }
/*

map、flatten*1、unitを実装。
どこに置いても良いが、Listクラスのstaticメソッドとして実装しておく。

 */
    public static <X, Y> List<Y> l_map(Function<X, Y> f, List<X> l) {
        ArrayList<Y> results = new ArrayList<Y>();
        for (X x : l.components) {
            results.add(f.apply(x));
        }
        return new List<Y>(results);
    }
    public static <X> List<X> l_flatten(List<List<X>> ll) {
        ArrayList<X> results = new ArrayList<X>();
        for (List<X> l : ll.components) {
            results.addAll(l.components);
        }
        return new List<X>(results);
    }
    public static <X> List<X> l_unit(X x) {
        ArrayList<X> results = new ArrayList<X>();
        results.add(x);
        return new List<X>(results);
    }

/*

合成しやすいように関数バージョンも用意しておく。
FunctionはGoogle guavaにあるものを借りている。
l_flattenやl_unitは定数でもいいんだけど総称型を生かすために一応メソッドにしておく。

 */
    public static <X, Y> Function<List<X>, List<Y>> l_map(final Function<X, Y> f) {
        return new Function<List<X>, List<Y>>() {
            public List<Y> apply(List<X> l) {
                return l_map(f, l);
            }
        };
    }
    public static <X> Function<List<List<X>>, List<X>> l_flatten() {
        return new Function<List<List<X>>, List<X>>() {
            public List<X> apply(List<List<X>> ll) {
                return l_flatten(ll);
            }
        };
    }
    public static <X> Function<X, List<X>> l_unit() {
        return new Function<X, List<X>>() {
            public List<X> apply(X x) {
                return l_unit(x);
            }
        };
    }
}

一応使用例兼テスト。
モナド則は「代数スタイル」の方*2です。
関数が等しいということは直接テストできないけど、とりあえず同じ入力に対する出力が一致することを確認する。

package sample.compositemonad;

import static org.junit.Assert.assertEquals;
import static sample.compositemonad.List.l_flatten;
import static sample.compositemonad.List.l_map;
import static sample.compositemonad.List.l_unit;

import org.junit.Test;
import com.google.common.base.Function;

public class ListTest {
    @Test
    public void testMap() {
        List<Integer> list1 = new List<Integer>(1, -3, 7);
        System.out.println("list1 = " + list1);
        Function<Integer, Integer> add1 = new Function<Integer, Integer>() {
            public Integer apply(Integer x) {
                return Integer.valueOf(x.intValue() + 1);
            }
        };
        List<Integer> list2 = l_map(add1, list1);
        System.out.println("list2 = " + list2);
        assertEquals(new List<Integer>(2, -2, 8), list2);
    }

    @Test
    public void testFlatten() {
        List<List<Integer>> ee =
            new List<List<Integer>>(
                new List<Integer>(0),
                new List<Integer>(1, -3, 7),
                new List<Integer>(1, 2)
            );
        System.out.println("ee = " + ee);
        List<Integer> result = l_flatten(ee);
        System.out.println("result = " + result);
        assertEquals(new List<Integer>(0, 1, -3, 7, 1, 2), result);
    }

    @Test
    public void testUnit() {
        assertEquals(new List<Integer>(1), l_unit(1));
        assertEquals(new List<String>("hoge"), l_unit("hoge"));
    }


    // 代数スタイルのモナド則
    // モナド則(1) l_flatten(l_unit(l)) = l
    @Test
    public void testMonad1() {
        List<Integer> list1 = new List<Integer>(1, -3, 7);
        assertEquals(l_flatten(l_unit(list1)), list1);
    }

    // モナド則(2) l_flatten(l_map(l_unit, l)) = l
    @Test
    public void testMonad2() {
        List<Integer> list1 = new List<Integer>(1, -3, 7);
        assertEquals(l_flatten(l_map(List.<Integer>l_unit(), list1)), list1);
    }

    // モナド則(3) l_flatten(l_flatten(lll)) = l_flatten(l_map(l_flatten, lll))
    @Test
    public void testMonad3() {
        List<List<List<Integer>>> lll = 
            new List<List<List<Integer>>>(
                new List<List<Integer>>(
                        new List<Integer>(0),
                        new List<Integer>(1, -3, 7),
                        new List<Integer>(1, 2)
                    ),
                new List<List<Integer>>(
                        new List<Integer>(3),
                        new List<Integer>(5, 6, 7),
                        new List<Integer>(2, 4)
                    )
            );
        System.out.println("lll = " + lll);
        assertEquals(
                l_flatten(l_flatten(lll)),
                l_flatten(l_map(List.<Integer>l_flatten(), lll)));
    }
}

print文(普通テストにはつけませんよー)の出力はこんな感じになります。

list1 = [1, -3, 7]
list2 = [2, -2, 8]
result = [0, 1, -3, 7, 1, 2]
lll = [[[0], [1, -3, 7], [1, 2]], [[3], [5, 6, 7], [2, 4]]]

Enumモナドの実装

java.lang.Enumと名前が被るけど気にせずEnumというクラス名にするよ。
中身は大体Listと同じ。但し要素はSetで保持し、要素の重複はおこらないようにする。

package sample.compositemonad;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import com.google.common.base.Function;

public class Enum<T> {
    private final Set<T> components;

    public Enum(Collection<T> components) {
        if (components == null) {
            throw new IllegalArgumentException("the arg should not be null");
        }
        this.components = Collections.unmodifiableSet(new HashSet<T>(components));
    }
    public Enum(T... components) {
        this(Arrays.asList(components));
    }
    public Enum(Enum<T> other) {
        this(other.components);
    }
    public final boolean equals(Object other) {
        return EqualsBuilder.reflectionEquals(this, other);
    }
    public final int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('{');
        Iterator<T> it = components.iterator();
        while (it.hasNext()) {
            sb.append(it.next().toString());
            if (it.hasNext()) sb.append(',').append(' ');
        }
        sb.append('}');
        return sb.toString();
    }

    public static <X, Y> Enum<Y> e_map(Function<X, Y> f, Enum<X> e) {
        HashSet<Y> results = new HashSet<Y>();
        for (X x : e.components) {
            results.add(f.apply(x));
        }
        return new Enum<Y>(results);
    }
    public static <X> Enum<X> e_flatten(Enum<Enum<X>> ee) {
        HashSet<X> results = new HashSet<X>();
        for (Enum<X> e : ee.components) {
            results.addAll(e.components);
        }
        return new Enum<X>(results);
    }
    public static <X> Enum<X> e_unit(X x) {
        HashSet<X> results = new HashSet<X>();
        results.add(x);
        return new Enum<X>(results);
    }

    // Function version
    public static <X, Y> Function<Enum<X>, Enum<Y>> e_map(final Function<X, Y> f) {
        return new Function<Enum<X>, Enum<Y>>() {
            public Enum<Y> apply(Enum<X> l) {
                return e_map(f, l);
            }
        };
    }
    public static <X> Function<Enum<Enum<X>>, Enum<X>> e_flatten() {
        return new Function<Enum<Enum<X>>, Enum<X>>() {
            public Enum<X> apply(Enum<Enum<X>> ee) {
                return e_flatten(ee);
            }
        };
    }
    public static <X> Function<X, Enum<X>> e_unit() {
        return new Function<X, Enum<X>>() {
            public Enum<X> apply(X x) {
                return e_unit(x);
            }
        };
    }
}

使用例というかテスト

package sample.compositemonad;

import static org.junit.Assert.assertEquals;
import static sample.compositemonad.Enum.e_flatten;
import static sample.compositemonad.Enum.e_map;
import static sample.compositemonad.Enum.e_unit;

import org.junit.Test;
import com.google.common.base.Function;

public class EnumTest {
    @Test
    public void testMap() {
        Enum<Integer> enum1 = new Enum<Integer>(1, -3, 7);
        System.out.println("enum1 = " + enum1);
        Function<Integer, Integer> add1 = new Function<Integer, Integer>() {
            public Integer apply(Integer x) {
                return Integer.valueOf(x.intValue() + 1);
            }
        };
        Enum<Integer> enum2 = e_map(add1, enum1);
        System.out.println("enum2 = " + enum2);
        assertEquals(new Enum<Integer>(2, -2, 8), enum2);
    }
    @Test
    public void testFlatten() {
        Enum<Enum<Integer>> ee =
            new Enum<Enum<Integer>>(
                new Enum<Integer>(0),
                new Enum<Integer>(1, -3, 7),
                new Enum<Integer>(1, 2)
            );
        System.out.println("ee = " + ee);
        Enum<Integer> result = e_flatten(ee);
        System.out.println("result = " + result);
        assertEquals(new Enum<Integer>(0, 1, -3, 7, 2), result);
    }
    @Test
    public void testUnit() {
        assertEquals(new Enum<Integer>(1), e_unit(1));
        assertEquals(new Enum<String>("hoge"), e_unit("hoge"));
    }

    // 代数スタイルのモナド則
    // モナド則(1) e_flatten(e_unit(e)) = e
    @Test
    public void testMonad1() {
        Enum<Integer> enum1 = new Enum<Integer>(1, -3, 7);
        assertEquals(e_flatten(e_unit(enum1)), enum1);
    }

    // モナド則(2) e_flatten(e_map(e_unit, e)) = e
    @Test
    public void testMonad2() {
        Enum<Integer> enum1 = new Enum<Integer>(1, -3, 7);
        assertEquals(e_flatten(e_map(Enum.<Integer>e_unit(), enum1)), enum1);
    }

    // モナド則(3) e_flatten(e_flatten(eee)) = e_flatten(e_map(e_flatten, eee))
    @Test
    public void testMonad3() {
        Enum<Enum<Enum<Integer>>> eee = 
            new Enum<Enum<Enum<Integer>>>(
                new Enum<Enum<Integer>>(
                        new Enum<Integer>(0),
                        new Enum<Integer>(1, -3, 7),
                        new Enum<Integer>(1, 2)
                    ),
                new Enum<Enum<Integer>>(
                        new Enum<Integer>(3),
                        new Enum<Integer>(5, 6, 7),
                        new Enum<Integer>(2, 4)
                    )
            );
        System.out.println("eee = " + eee);
        assertEquals(
                e_flatten(e_flatten(eee)),
                e_flatten(e_map(Enum.<Integer>e_flatten(), eee)));
    }
}

print文(普通テストにはつけませんよー)の出力はこんな感じになります。

enum1 = {1, 7, -3}
enum2 = {2, 8, -2}
ee = {{0}, {1, 7, -3}, {1, 2}}
result = {0, 1, 2, 7, -3}
eee = {{{2, 4}, {5, 6, 7}, {3}}, {{0}, {1, 7, -3}, {1, 2}}}

combinationsの実装

List>からEnum>を作る関数combinationsを実装する。
場所はどこでもいいんだけどUtilというクラスを作ってstaticメソッドとして実装する。

package sample.compositemonad;

import static sample.compositemonad.Enum.e_flatten;
import static sample.compositemonad.Enum.e_unit;
import static sample.compositemonad.Enum.e_map;

import com.google.common.base.Function;
public final class Util {
    public static <X> Enum<List<X>> combinations(List<Enum<X>> le) {
        return _combinations(new List<X>(), le);
    }
    private static <X> Enum<List<X>> _combinations(final List<X> path, List<Enum<X>> le) {
        if (le.isEmpty()) {
            // すべて選び終えているのでpathをEnumで包んで戻す。
            return e_unit(path);
        } else {
            Enum<X> head = le.head();
            final List<Enum<X>> tail = le.tail();

            // headの各要素に対してpathに継ぎ足して残りのList<Enum<X>>と一緒に_combinations()を呼び出し
            // 結果をまとめて戻す。
            return e_flatten(e_map(new Function<X, Enum<List<X>>>() {
                public Enum<List<X>> apply(X x) {
                    return _combinations(path.appended(x), tail);
                }
            }, head));
        }
    }
    // Function Version
    public static <X> Function<List<Enum<X>>, Enum<List<X>>> combinations() {
        return new Function<List<Enum<X>>, Enum<List<X>>>() {
            public Enum<List<X>> apply(List<Enum<X>> le) {
                return combinations(le);
            }
        };
    }
}

combinationsの出力結果は、List>の先頭から末尾までのEnumから順番に要素Xをひとつずつ選んでListをつくり、そのすべての組み合わせをEnumにしたものになる。
_combinations()は、例えばk番目まで選んだ状態で、1〜k番の要素をListにしたものをpathとして渡し、残りのまだ選ばれていないk+1〜n番目のEnumをList> leとして渡す形で再帰で実装している。
折角なので上で実装したe_flatten、e_map、e_unitを使ってるけど特に意味は無い。ここでは実装方法は今回は本質ではないので。


使用例というかテスト

package sample.compositemonad;
import static org.junit.Assert.assertEquals;
import static sample.compositemonad.Util.combinations;
import static sample.compositemonad.List.*;
import static sample.compositemonad.Enum.*;

import org.junit.Test;

public class UtilTest {
    @Test
    public void testCombinations() {
        List<Enum<Integer>> le = new List<Enum<Integer>>(
            new Enum<Integer>(0),
            new Enum<Integer>(1, -3, 7),
            new Enum<Integer>(1, 2)
        );
        System.out.println("le = " + le);
        Enum<List<Integer>> el = combinations(le);
        System.out.println("el = " + el);
        
        Enum<List<Integer>> expected = new Enum<List<Integer>>(
                new List<Integer>(0, 1, 1),
                new List<Integer>(0, 1, 2),
                new List<Integer>(0, -3, 1),
                new List<Integer>(0, -3, 2),
                new List<Integer>(0, 7, 1),
                new List<Integer>(0, 7, 2)
        );
        assertEquals(expected, el);
    }
    @Test
    public void testCombinations_withEmpty() {
        List<Enum<Integer>> le = new List<Enum<Integer>>(
            new Enum<Integer>(0),
            new Enum<Integer>(),
            new Enum<Integer>(1, 2)
        );
        System.out.println("le = " + le);
        Enum<List<Integer>> el = combinations(le);
        System.out.println("el = " + el);
        assertEquals(new Enum<List<Integer>>() , el);
    }
/*

ベックの法則も一応サンプルデータで確認。

 */
    // combinations(l_unit(enu)) = e_map(l_unit, enu)
    @Test
    public void testBeckTheorem1() {
        Enum<Integer> enu = new Enum<Integer>(1, -3, 7);
        assertEquals(
            combinations(List.<Enum<Integer>>l_unit(enu)),
            e_map(List.<Integer>l_unit(), enu)
        );
    }
    // combinations(l_map(e_unit, lis)) = e_unit(lis)
    @Test
    public void testBeckTheorem2() {
        List<Integer> lis = new List<Integer>(1, -3, 7);
        assertEquals(
            combinations(l_map(Enum.<Integer>e_unit(), lis)),
            e_unit(lis)
        );
    }
    // e_flatten(e_map(combinations, combinations(lee))) = combinations(l_map(e_flatten, lee))
    @Test
    public void testBeckTheorem3() {
        List<Enum<Enum<Integer>>> lee = new List<Enum<Enum<Integer>>>(
                new Enum<Enum<Integer>>(
                    new Enum<Integer>(0),
                    new Enum<Integer>(1, -3, 7),
                    new Enum<Integer>(4, 5)
                ),
                new Enum<Enum<Integer>>(
                    new Enum<Integer>(0),
                    new Enum<Integer>(1, 2)
                )
            );
        assertEquals(
            e_flatten(e_map(Util.<Integer>combinations(), combinations(lee))),
            combinations(l_map(Enum.<Integer>e_flatten(), lee))
        );
    }
    // e_map(l_flatten, combinations(l_map(combinations, lle))) = combinations(l_flatten(lle))
    @Test
    public void testBeckTheorem4() {
        List<List<Enum<Integer>>> lle = new List<List<Enum<Integer>>>(
                new List<Enum<Integer>>(
                    new Enum<Integer>(0),
                    new Enum<Integer>(1, -3, 7),
                    new Enum<Integer>(4, 5)
                ),
                new List<Enum<Integer>>(
                    new Enum<Integer>(0),
                    new Enum<Integer>(1, 2)
                )
            );
        assertEquals(
                e_map(List.<Integer>l_flatten(), combinations(l_map(Util.<Integer>combinations(), lle))),
                combinations(l_flatten(lle))
        );
    }
}

複合モナド EnumOfList の実装

主旨は複合モナドEnum>がflatten/map/unitとcombinationsで作れますよということなんだけど、がんばってEnum>のサブクラスEnumOfListという実クラスの形で実装してみる。

package sample.compositemonad;

import static sample.compositemonad.List.l_map;
import static sample.compositemonad.List.l_unit;

import java.util.Collection;

import com.google.common.base.Function;

public class EnumOfList<X> extends Enum<List<X>> {
    public EnumOfList(Collection<List<X>> components) {
        super(components);
    }
    public EnumOfList(List<X>... components) {
        super(components);
    }
    public EnumOfList(Enum<List<X>> el) {
        super(el);
    }
/*

mapとunitは素直に書ける。new EnumOfList(...)は中のEnum>をEnumOfListにするのに付けているだけなので、実質括弧の中の式が定義通りになっているのが分かる。

 */
    // el_map(f, el) := e_map(l_map(f), el)
    public static <X, Y> EnumOfList<Y> el_map(Function<X, Y> f, EnumOfList<X> el) {
        return new EnumOfList<Y>(
              e_map(l_map(f), el)
        );
    }
    // el_unit(x) := e_unit(l_unit(x))
    public static <X> EnumOfList<X> el_unit(X x) {
        return new EnumOfList<X>(
              e_unit(l_unit(x))
        );
    }
/*

flattenの実装。まず定義どおりに書いてみる。

 */
    // el_flatten(elel) := e_flatten(e_map(e_map(l_flatten), e_map(combinations, elel)))
    public static <X> EnumOfList<X> el_flatten(EnumOfList<EnumOfList<X>> elel) {
      return new EnumOfList<X>(
              e_flatten(
                  e_map(
                      e_map(List.<X>l_flatten()), 
                      e_map(Util.<List<X>>combinations(), elel)
                    //↑ここのe_mapで型の不一致が起こる。
      )));
    }
}

コンパイルエラーになって怒られる。どうやらEnumOfList>がEnum>>>であるのが分からないらしい。(特に内側のEnumOfListEnum>であることが分からない。*3 ) 仕方が無いので型パラメータを増やしてヒントを出してあげる。

public final class Util {
    // 型パラメータEを追加
    public static <X, E extends Enum<X>> Enum<List<X>> combinations(List<E> le) {
        return combinations(new List<X>(), le);
    }
    private static <X, E extends Enum<X>>
            Enum<List<X>> combinations(final List<X> path, List<E> le) {

        if (le.isEmpty()) {
            return e_unit(path);
        } else {
            Enum<X> head = le.head();
            final List<E> tail = le.tail();

            return e_flatten(e_map(new Function<X, Enum<List<X>>>() {
                public Enum<List<X>> apply(X x) {
                    return combinations(path.appended(x), tail);
                }
            }, head));
        }
    }
    public static <X, E extends Enum<X>> Function<List<E>, Enum<List<X>>> combinations() {
        return new Function<List<E>, Enum<List<X>>>() {
            public Enum<List<X>> apply(List<E> le) {
                return combinations(le);
            }
        };
    }
}

呼び出す側。上で追加した型パラメータEにEnumOfListを指定する。

    public static <X> EnumOfList<X> el_flatten(EnumOfList<EnumOfList<X>> elel) {
      return new EnumOfList<X>(
              e_flatten(
                  e_map(
                      e_map(List.<X>l_flatten()), 
                      e_map(Util.<List<X>, EnumOfList<X>>combinations(), elel)
      )));
    }

これでコンパイルが通った。
Functionを直せば対応できるような気がするけど今回はまあいいか。


関数バージョンも用意しておく

public class EnumOfList<X> extends Enum<List<X>> {
...
    // Function version
    public static <X, Y> Function<EnumOfList<X>, EnumOfList<Y>> el_map(final Function<X, Y> f) {
        return new Function<EnumOfList<X>, EnumOfList<Y>>() {
            public EnumOfList<Y> apply(EnumOfList<X> el) {
                return el_map(f, el);
            }
        };
    }
    public static <X> Function<X, EnumOfList<X>> el_unit() {
        return new Function<X, EnumOfList<X>>() {
            public EnumOfList<X> apply(X x) {
                return el_unit(x);
            }
        };
    }
    public static <X> Function<EnumOfList<EnumOfList<X>>, EnumOfList<X>> el_flatten() {
        return new Function<EnumOfList<EnumOfList<X>>, EnumOfList<X>>() {
            public EnumOfList<X> apply(EnumOfList<EnumOfList<X>> el) {
                return el_flatten(el);
            }
        };
    }
}

各関数とモナド則(代数スタイル)のテスト。
関数が等しいということは直接テストできないけど、とりあえず同じ入力に対する出力が一致することを確認する。

package sample.compositemonad;

import static org.junit.Assert.assertEquals;
import static sample.compositemonad.EnumOfList.el_flatten;
import static sample.compositemonad.EnumOfList.el_map;
import static sample.compositemonad.EnumOfList.el_unit;

import org.junit.Test;
import com.google.common.base.Function;

public class EnumOfListTest {

    // テストデータ
    EnumOfList<Integer> el1 = new EnumOfList<Integer>(
            new List<Integer>(0),
            new List<Integer>(1, -3, 7),
            new List<Integer>(1, 2)
    );
    // テストデータ
    EnumOfList<EnumOfList<Integer>> elel = new EnumOfList<EnumOfList<Integer>>(
            new List<EnumOfList<Integer>>(
                new EnumOfList<Integer>(        // {[0], [1, -3, 7], [4, 5]}
                    new List<Integer>(0),
                    new List<Integer>(1, -3, 7),
                    new List<Integer>(4, 5)
                ),
                new EnumOfList<Integer>(        //  {[0], [1, 2]}
                    new List<Integer>(0),
                    new List<Integer>(1, 2)
                )
            ),
            new List<EnumOfList<Integer>>(
                new EnumOfList<Integer>(        // {[0], [1], [2]}
                    new List<Integer>(0),
                    new List<Integer>(1),
                    new List<Integer>(2)
                ),
                new EnumOfList<Integer>(        //  {[1], [2]}
                    new List<Integer>(1),
                    new List<Integer>(2)
                )
            )
        );

    @Test
    public void testFlatten() {
        System.out.println("elel = " + elel);
        EnumOfList<Integer>el = el_flatten(elel);
        System.out.println("el = " + el);

        EnumOfList<Integer> expected = new EnumOfList<Integer>(
            // {[0], [1, -3, 7], [4, 5]} x {[0], [1, 2]}
                // [0] x {[0], [1, 2]}
                new List<Integer>(0,        0),
                new List<Integer>(0,        1, 2),
                // [1, -3, 7] x {[0], [1, 2]}
                new List<Integer>(1, -3, 7, 0),
                new List<Integer>(1, -3, 7, 1, 2),
                // [4, 5] x {[0], [1, 2]}
                new List<Integer>(4, 5,     0),
                new List<Integer>(4, 5,     1, 2),
            //  {[0], [1], [2]} x {[1], [2]}
                // [0] x {[1], [2]}
                new List<Integer>(0,        1),
                new List<Integer>(0,        2),
                // [1] x {[1], [2]}
                new List<Integer>(1,        1),
                new List<Integer>(1,        2),
                // [2] x {[1], [2]}
                new List<Integer>(2,        1),
                new List<Integer>(2,        2)
        );
        assertEquals(expected, el);
    }
    @Test
    public void testMap() {
        System.out.println("el1 = " + el1);
        Function<Integer, Integer> add1 = new Function<Integer, Integer>() {
            public Integer apply(Integer x) {
                return Integer.valueOf(x.intValue() + 1);
            }
        };
        EnumOfList<Integer> el2 = el_map(add1, el1);
        System.out.println("el2 = " + el2);
        EnumOfList<Integer> expected = new EnumOfList<Integer>(
                new List<Integer>(1),
                new List<Integer>(2, -2, 8),
                new List<Integer>(2, 3)
        );
        assertEquals(expected, el2);
    }
    @Test
    public void testUnit() {
        assertEquals(new EnumOfList<Integer>(new List<Integer>(1)), el_unit(1));
        assertEquals(new EnumOfList<String>(new List<String>("hoge")), el_unit("hoge"));
    }

    // el_flatten(el_unit(el)) = el
    @Test
    public void testMonad1() {
        assertEquals(el_flatten(el_unit(el1)), el1);
    }

    // el_flatten(el_map(el_unit, el)) = el
    @Test
    public void testMonad2() {
        assertEquals(el_flatten(el_map(EnumOfList.<Integer>el_unit(), el1)), el1);
    }

    // el_flatten(el_flatten(elel)) = el_flatten(el_map(el_flatten, elel))
    @Test
    public void testMonad3() {
        EnumOfList<EnumOfList<EnumOfList<Integer>>> elelele = 
            new EnumOfList<EnumOfList<EnumOfList<Integer>>>(
                    new List<EnumOfList<EnumOfList<Integer>>>(elel),
                    new List<EnumOfList<EnumOfList<Integer>>>(el_unit(el1))
            );
        System.out.println("elelele = " + elelele);
        assertEquals(
                el_flatten(el_flatten(elelele)),
                el_flatten(el_map(EnumOfList.<Integer>el_flatten(), elelele)));
    }
}

el_flattenは中のelの各要素を掛け算のように組み合わせ、Listとしてつなげたものになっている。
3つ目はel_flatten(el_flatten(elel))だとシグネチャが合わないのでelelelで動かしている。

自分用宿題

  • 共変のなぞを解いて型パラメータを増やさずコンパイルが通るようにする。
  • 拡張スタイルの関数(というかbind)の導出→出来ました

*1:flatternはtypoだと思うので勝手に直しました

*2:cf: http://d.hatena.ne.jp/m-hiyama/20060418/1145322223

*3:共変ではないため