Javaプログラマーのための圏論(1.圏)
圏
圏は対象(object)と対象から対象へ進む矢印(arrow)から構成される。対象は丸印または点で表し、矢印は矢印(有向辺)で表す。圏の本質は合成であり、合成の本質は合成と言える。対象Aから対象Bへの矢印と対象Bから対象Cへの矢印があれば、これらの合成であるAからCへの矢印が存在する。
矢印
関数としての矢印は、射と呼ばれる。型Aを引数とし型Bを戻り値とする関数$f$と型Bを引数とし型Cを戻り値とする関数$g$を合成することで、型Aを引数とし型Cを戻り値とする新たな関数を定義することができる。このような合成を$f \circ g$と記す。
javaで書いてみると、次のようになる。
B f(A a)
C g(B b)
C g_after_f(A a)
{
return g(f(a));
}
Java8以降であれば、次のように書ける。
Function<A,B> f; Function<B,C> g; g.compose(f);
合成
圏では、次の合成に関する2つの重要な性質を満たさなければならない。
1. 合成の結合
$f$,$g$,$h$の3つの射があり、合成できるのであれば、これらを合成する際に括弧は不要である。
$$
h \circ (g \circ f) = (h \circ g) \circ f = h \circ g \circ f
$$
1. 任意の対象Aに対して、合成の単位元である矢印が存在する。この矢印は対象からそれ自身へのループの矢印である。合成の単位元と言うのは、対象Aで始まる矢印、対象Aで終わる矢印と合成すると、それぞれ同じ矢印と一致すると言う意味である。対象Aに対する単位元を$id_A$と表記し、A上の恒等射と呼ぶ。AからBへの矢印$f$に対して、
$$
f \circ id_A = f
$$
かつ
$$
id_A \circ f = f
$$
となる。
恒等射を実装すると、引数をそのまま返す恒等関数となる。どのような型に対しても実装は同じであり、ユニバーサルにポリモルフィクである。Javaでは次のように実装することができる。
<T> T id(T x) { return x; }
Java8以降であれば、Functon、UnaryOperatorにidentity()が定義されているので、
Function<T,T> id = Function.identity(); UnaryOperator<T> id = UnaryOperator.identity();
として表すことできる。 単位元の条件は、Javaで疑似的に表現すると、次のようになる。
Function<A,B> f; Function<A,A> id_A = Function.identity(); Function<B,B> id_B = Function.identity(); f.compose(id_A) == f id_B.compose(f) == f
恒等射idは、数字の0(零)と何もしないことを表すシンボルで、極めて役に立つものである。
プログラミングのエッセンスである合成
関数型プログラミングを行う際には、特異な問題解決のアプローチを行う。自明でない問題を解決する際には、大きな問題を小さい問題に分解する。分解した問題がまだ大きければ、さらに小さい問題に分解する。最終的には小さい問題を解決するプログラムのコードを書くことになる。プログラミングのエッセンスは、こうした分解した断片のコードを合成することによって、大きな問題を解決するのである。分解した断片が元に戻せなければ意味がない。