続・比較モナド
処理の流れを制御することが目的なら確かにOption(Maybeモナド)で十分だったかもしれない。
前回のやつを書き直してみた。
Optionの実装
Javaでかくとこんな感じだよ。*1
orElseは多分mplusになるのでmplusってメソッド名にしてます。
package sample.maybe; import java.util.NoSuchElementException; import org.apache.commons.lang.Validate; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; import com.google.common.base.Function; @SuppressWarnings({"unchecked", "rawtypes"}) public final class Options { public interface Option<T> { public T get(); public boolean isEmpty(); public <S> Option<S> bind(Function<T, Option<S>> func); public Option<T> mplus(Option<T> other); } public static <T> Option<T> someOf(T value) { return new Some(value); } private static Option NONE = new None(); public static <T> Option<T> none() { return (Option<T>)NONE; } public static <T> Option<T> unit(T element) { return someOf(element); } public static <T> Option<T> mzero() { return none(); } public static abstract class AbstractOption<T> implements Option<T> { public final boolean equals(Object other) { return EqualsBuilder.reflectionEquals(this, other); } public final int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } public String toString() { return ToStringBuilder.reflectionToString(this); } } final static class Some<T> extends AbstractOption<T> { private final T value; Some(T value) { Validate.notNull(value); this.value = value; } @Override public T get() { return value; } @Override public boolean isEmpty() { return false; } @Override public <S> Option<S> bind(Function<T, Option<S>> func) { return func.apply(get()); } @Override public Option<T> mplus(Option<T> other) { return this; } } final static class None<T> extends AbstractOption<T> { None() { } @Override public T get() { throw new NoSuchElementException(); } @Override public boolean isEmpty() { return true; } @Override public <S> Option<S> bind(Function<T, Option<S>> func) { return (Option<S>) this; } @Override public Option<T> mplus(Option<T> other) { return other; } } }
Some#mplus(Option other)の結果はthis, None#mplus(Option other)の結果はotherになるようにしている。
Option版Comparator
int型の符号ではなくOptionを戻すComparatorを定義する。
public interface ComparatorO<T> { /** * left,rightの比較を行い、左側が小さい場合Options.someOf(負の数)、 * 左側が大きい場合Options.someOf(正の数)、等しい場合Options.someOf(0)、 * 不定の場合にOptions.none()の結果を戻す。 * @return Optionで結果を戻す。 */ Option<Integer> compare(T left, T right); }
使用例
前回のWeblogEntriesComparatorをComparatorOを使って実装してみる。
// (1)pubDateの降順、(2)idの昇順、という順序でソートする時のComparator public class WeblogEntriesComparator implements Comparator<WeblogEntries> { // pudDate で比較するComparatorO private ComparatorO<WeblogEntries> pubDateComparator = new ComparatorO<WeblogEntries>() { public Option<Integer> compare(WeblogEntries left, WeblogEntries right) { int sign = left.getPubDate().compareTo(right.getPubDate()); return sign != 0 ? Options.someOf(-1 * sign) : Options.<Integer>none(); } }; // id で比較するComparatorO private ComparatorO<WeblogEntries> idComparator = new ComparatorO<WeblogEntries>() { public Option<Integer> compare(WeblogEntries left, WeblogEntries right) { int sign = left.getId().compareTo(right.getId()); return sign != 0 ? Options.someOf(sign) : Options.<Integer>none(); } }; @Override public int compare(WeblogEntries entry1, WeblogEntries entry2) { return pubDateComparator.compare(entry1, entry2) .mplus(idComparator.compare(entry1, entry2)) .mplus(Options.someOf(0)).get(); } }
「簡単にソート順を入れ替えたり追加したりしたい」という目的には、まあこれで十分な気がする。
引数がEagerに評価されてしまうので処理的に無駄が出てしまうけど。