2014年3月22日土曜日

Java8 で Monoid を 書いてみた

春といえば Monoid。

前回までで、Java8 で関数の部分適用とカリー化を書いた。
今度は Monoidでも書いてみるか。春だからな。
import java.util.stream.Stream;
import java.util.function.BinaryOperator;

interface Monoid<A> {
  A empty();
  BinaryOperator<A> append();
  default A concat(Stream<A> s) {
    return s.reduce(empty(), append());
  }

  default A append(A p1, A p2) {
    return append().apply(p1, p2);
  }
  static <A> Monoid<A> of(A empty, BinaryOperator<A> append) {
    return new Monoid<A>() {
      public A empty() { return empty; }
      public BinaryOperator<A> append() { return append; }
    };
  }
}
  • empty() は 単位元。Haskell の mempty。
  • append() は 二項演算で、Haskell の mappend。
  • append(A, A)は、いちいち apply()を呼ばなくてもすむようにしただけ。
  • concat() は Haskell の mconcat で、受け取る型は配列でもコレクションでもよかったけど、ここでは Stream にしてみた。
  • of() はファクトリメソッド。

まずは単純に文字列の結合で試してみる。モノイドはこんな感じ。
  private static final Monoid<String> stringMonoid =
      Monoid.of("", (s1, s2) -> s1 + s2);
で、こうなる。
    Stream<String> s = Stream.of("ab", null, "cd");
    System.out.println(stringMonoid.concat(s));// "abnullcd"

次に、Haskellの Ordering を試してみよう。

元ネタは Learn You a Haskell の Monoid の 『The Ordering monoid』のとこ。
とりあえず Ordering はこんな感じの列挙子にしてみた。
enum Ordering {
  LT, EQ, GT; 
  public static Ordering fromInt(int n) {
    return n < 0 ? Ordering.LT :
           n > 0 ? Ordering.GT : Ordering.EQ;
  }
}
で、Monoid はこう書いてみた。
  private static final Monoid<Ordering> orderingMonoid =
    Monoid.of(EQ, (a, b) -> EQ.equals(a) ? b :
                            LT.equals(a) ? LT: GT);
この Monoidを使うコードが、元ネタにも載ってるこんなメソッド。
  private static Ordering lengthCompare(String s1, String s2) {
    return orderingMonoid.append(
      compareByLength(s1, s2), compareAlphabetically(s1, s2));  
  }
まあ、どうでもいいけど compareByLength() と compareAlphabetically() は一応こんな感じ。(簡単のため、null チェックとか省略)
  private static Ordering compareByLength(String s1, String s2) {
    return fromInt(s1.length() - s2.length());
  }
  private static Ordering compareAlphabetically(String s1, String s2) {
    return fromInt(s1.compareTo(s2));   
  }
実行すると期待通りに動く
    System.out.println(lengthCompare("zen", "ants")); // LT
    System.out.println(lengthCompare("zen", "ant")); // GT
    System.out.println(lengthCompare("zen", "zen")); // EQ
実際に使うとしたら、Monoids クラスみたいなやつに、主だったモノイドオブジェクトを置いておく事になるかもしれない。

0 件のコメント:

コメントを投稿