続・比較モナド

モナドのこと検索してたら次のようなtweet見かけた。

処理の流れを制御することが目的なら確かに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に評価されてしまうので処理的に無駄が出てしまうけど。

*1:前に書いたのとちょっと違う気もするけど。