Maybe Java りたーんず 〜 Java で Maybe を書いてみた

モナド則を満たすものがやっとできた。

定義

interface Function<T, R> {
    public R eval(T arg);
}

abstract class Monad<T, M> {
    public abstract <R extends M> R bind(Function<T, R> func);
}

abstract class Maybe<T> extends Monad<T, Maybe<?>> {
    public static <T> Maybe<T> unit(T value) {
        return new Just<T>(value);
    }
}

class Just<T> extends Maybe<T> {
    private final T value;
    
    public Just(T value) {
        this.value = value;
    }
    
    @Override
    public <R extends Maybe<?>> R bind(Function<T, R> func) {
        return func.eval(value);
    }
    
    @Override
    public String toString() {
        return String.format("Just(%s)", value);
    }
}

class Nothing extends Maybe<Object> {
    @Override
    @SuppressWarnings("unchecked")
    public <R extends Maybe<?>> R bind(Function<Object, R> func) {
        return (R) this;
    }
    
    @Override
    public String toString() {
        return "Nothing";
    }
}

用意するものは次の3個の関数。

Function<Integer, Maybe<Integer>> inc = new Function<Integer, Maybe<Integer>>() {
    public Maybe<Integer> eval(Integer arg) { return Maybe.unit(arg + 1); }
};
Function<Integer, Maybe<String>> tostr = new Function<Integer, Maybe<String>>() {
    public Maybe<String> eval(Integer arg) { return Maybe.unit("num = " + arg.toString()); }
};
Function<Integer, Maybe<Integer>> ret = new Function<Integer, Maybe<Integer>>() {
    public Maybe<Integer> eval(Integer arg) { return Maybe.unit(arg); }
};

検証

モナド則を検証していく。まずは「(unit x) >>= f == f x」から。

System.out.println(ret.eval(1).bind(inc));     // #-> Just(2)
System.out.println(inc.eval(1));               // #-> Just(2)

次に「m >>= unit == m」

System.out.println(ret.eval(1).bind(ret));     // #-> Just(1)
System.out.println(ret.eval(1));               // #-> Just(1)

最後は「(m >>= f) >>= g == m >>= (\x -> f x >>= g)」

System.out.println(ret.eval(1).bind(inc).bind(tostr));    // #-> Just(num = 2)
System.out.println(ret.eval(1).bind(new Function<Integer, Maybe<String>>() {
    public Maybe<String> eval(Integer arg) { return inc.eval(arg).bind(tostr); }
}));    // #-> Just(num = 2)

不満

class Monad の M に制約を付けられなかったのが残念。M extends Monad みたいにしたかったんだけど上手くいかず。どういう風に書けばいいのかな。
あと Nothing の SuppressWarnings も気になるところ。難しいね。

雑感

bind に渡す関数の型は a -> m a だった。Haskell ならば return 関数が m a 型の値を返すから、普通は (\x -> return x + 1) のような形になる。そして、do 記法によって (\x -> return x + 1) の大部分が省略され、あとは

do x <- m
   x + 1

と書いてしまうことができる、はず。
ということは、モナドの本質は Monad だけじゃなくて、do を含めて初めて完全になるのではないかと思ったり。もう少し考えてみよう。