Javaプログラマに「モナドがなんの役に立つのか」を説明するために、この記事を書きました。
#メソッド内の副作用とは
こんなのだったり
public int getAge() {
age += 1; //やめろー!
return age;
}
こんなのだったり
public double calculateTax(double price) {
System.out.println("raw price:" + price); //え、表示もするの…?
return price * 0.08;
}
こんなのだったり
public boolean isLegalPerson(Person person) {
if (person.getType().equals(PersonType.UNKNOWN))
throw new RuntimeException(); //せめて検査例外にして…
return person.getType().equals(PersonType.LEGAL);
}
ようはメソッド(≒関数)のインプット→アウトプット以外の作用は全て副作用です。
もちろん副作用が全て悪いというわけではないです。
でも予期しない副作用は悪です!!!(上記のような例)
#じゃあ、関数内に副作用が存在することをどう表明する?
- ドキュメントに書いておく → ドキュメントは書かれない、陳腐化もする
- アノテーションを付けておく → 付け忘れや、必要ないのに付けたりする
では、関数に副作用があるということを戻り値の型で表現できたらいいのでは?
→そのためのデザインパターンが「モナド」です
#戻り値型で副作用の存在が表現できれば…
//型を見るだけで値の書き換えがあることがわかる!
public State<Integer> getAge() {
...
}
//型を見るだけで表示も行うことがわかる!
public IO<Double> calculateTax(double price) {
....
}
//型を見るだけで例外を投げることがわかる!
public Try<Boolean> isLegalPerson(Person person) {
....
}
では、この仕組みをどう実現するか?
#モナド様「言語内DSLを使うんじゃ!」
モナドパターンで副作用が存在することを表明するための言語内DSLが作れます。
つまり「戻り値型で副作用の存在を示す」という用途に特化したミニ言語(DSL)、を作るためのデザインパターンが、モナド。1
※ただし言語内DSLの語彙を使わずに
System.out.println("モナドなんて知るかっ!");
throw new RuntimeException("モナドなんて知るかっ!");
とか普通に書いてしまったら、モナドパターンを使う意味がなくなる。
なので、モナドパターンで作られた言語内DSLを使って、その枠の中でプログラミングする必要がある。そうしないと「副作用の存在を表明する」ことができないので注意。
(Spring MVC使ってるのに直接ServletAPIを触るな、Spring MVCを使ってる意味がねえ、みたいな感じ)
#StateモナドのJava実装(Java8版)
実例として、Javaでモナドパターンを使って**「関数内に状態があることを戻り値型で表現できる言語内DSL」**を作ってみる。
package monad;
import java.util.function.Function;
class Pair<A> {
public final Integer state;
public final A value;
Pair(A a, Integer s) {
this.value = a;
this.state = s;
}
}
public class State<A> {
public Function<Integer, Pair<A>> func;
public State(Function<Integer, Pair<A>> func) { this.func = func; }
public static <A> State<A> unit(A a) {
return new State<>(s -> new Pair<>(a, s));
}
public <B> State<B> bind(Function<A, State<B>> nextFunc) {
return new State<>(s -> {
Pair<A> pair = func.apply(s);
return nextFunc.apply(pair.value).func.apply(pair.state);
});
}
public static State<Integer> get() {
return new State<>(s -> new Pair<>(s, s));
}
public static State<Void> put(final Integer s) {
return new State<>(__ -> new Pair<>(null, s));
}
}
Java版Stateモナドの使い方
import static monad.State.get;
import static monad.State.put;
import static monad.State.unit;
public class MonadicExample {
//戻り値の型に副作用の存在が表現される
public static State<Integer> getAge() {
//この言語内DSLでは代入演算子の代わりにbindメソッドを使う
return get().bind(age ->
put(age + 1).bind(__ ->
get()));
}
public static void main(String[] args) {
//2回getAgeを呼び出しているので、2が表示される
getAge().bind(__ ->
getAge().bind(age -> {
System.out.println(age);
return unit(null);
})).func.apply(0);
}
}
Javaのモナドはちょっと(かなり?)見にくい…Javaの限界
同じ処理をStateモナドを使わずに書くとこうなる
public class NonMonadicExample {
private static int age = 0;
public static int getAge() {
age = age + 1; //この副作用の存在に気づけない!!
return age;
}
public static void main(String[] args) {
//2回getAgeを呼び出しているので、2が表示される
getAge();
System.out.println(getAge());
}
}
#Scalaだともっと書きやすいよ
関数型言語ではモナドパターンで作られた言語内DSLを書きやすくするための専用syntax sugarが存在する
scalaだとモナドDSL専用のsyntax sugarにより、こんな感じで書ける。
def getAge(): State[Int] = for {
age <- get()
_ <- put(age + 1)
age <- get()
} yield age
(for {
_ <- getAge()
age <- getAge()
} yield println(age)).run(0)
Stateモナド以外のモナドパターンの詳細について知りたい方は「Scala関数型デザイン&プログラミング」などの本を読みましょう!
https://www.amazon.co.jp/dp/B00WM54V5Q/
#「でも…モナドって関数型言語の話でしょ?俺には関係ないわ…」
そうとは言い切れないぞ~
Java8から追加されたOptional型、CompletableFuture型、StreamAPI、また最近流行のRxJava(リアクティブプログラミング用のライブラリ)のObservableも、実はモナドパターンで実装されています。scalaのような専用構文が無いので書きにくいけど。
あと、Javaでモナドを書きやすくする仕組みとして以下のようなライブラリもあるようです。
https://github.com/soabase/soabase-halva/blob/master/halva/src/main/java/io/soabase/halva/comprehension/README.md
http://fits.hatenablog.com/entry/2015/05/16/204101
-
クライスリ圏の話とかしだす怖い人はどっかに行ってください… ↩