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(零)と何もしないことを表すシンボルで、極めて役に立つものである。

プログラミングのエッセンスである合成

 関数型プログラミングを行う際には、特異な問題解決のアプローチを行う。自明でない問題を解決する際には、大きな問題を小さい問題に分解する。分解した問題がまだ大きければ、さらに小さい問題に分解する。最終的には小さい問題を解決するプログラムのコードを書くことになる。プログラミングのエッセンスは、こうした分解した断片のコードを合成することによって、大きな問題を解決するのである。分解した断片が元に戻せなければ意味がない。