Javaプログラマーのための圏論(2.型と関数)

型と関数

型の合成について

 圏論の本質は矢印の合成である。必ずしも2つの矢印が合成できるとは限らない。ある矢印の終端の対象が次の矢印の始点の対象と一致していなければならない。プログラミングではある関数の結果が別の関数へ通過させることになる。ある関数によって生成されたデータが次の関数によって正確に解釈されなかれば、プログラムは動かない。二つの端は合成するために適合しなければならない。言語の型システムが堅くなればなるほど、この適合が表現しやすくなるし、機械的に検証もされやすくなる。

型とは何か?

 型は最もシンプルな直観的には値の集合である。型BooleanはTRUEとFALSEの2つの要素である。
 集合は有限集合も無限集合もありうる。StringはCharacterのリストであるが、無限集合である。IntegerはJavaでは $-2^{31}$から $2^{31}-1$の間なので有限集合である。
 型と集合の識別は扱いにくい微妙なところがある。ポリモルフィックな関数は循環定義を必要とする問題があり、すべての集合の集合は存在しないと言う事実がある。重要なことは集合の圏が存在し、それは$Set$と呼ばれる。$Set$では、対象は集合であり、射(矢印)は関数である。
 $Set$は特殊な圏で、対象の中をのぞき見たり、実際にそうすることで多くの直感が得られるためである。例えば、空集合は要素を持たない。1つの要素しか持たない集合もある。ある集合から別の集合への関数もある。2つの要素の集合から1つの要素の集合への関数もあるし、1つの要素の集合から2つの要素の集合への関数もある。恒等関数は集合の各要素を自身へ移す。
 徐々に全ての情報は忘れて、圏論の記号、対象と矢印で表現していく予定である。

型の例

 幾つかの風変わりな型について考えてみよう。空集合に対応する型は何だろうか?Javaのvoidではない。どのような値も継承しないし、その型を引数にして呼び出すことができない型である。引数にして呼び出すためには、その型の値を与える必要があるが、この型は値を持たない。この関数はどのような値を戻すかと言う点でなんの制約もない。この関数はどのような型も戻すことできる(但し、呼び出されることができないので、そのようなことは起きないが)別の言葉で言えば、その関数は戻り値の型においてポリモルフィックな関数と言える。
 次にシングルトン集合に対応する型を考えてみよう。この型は唯一つだけ値を持つことが出来る。Javaでは void である。この型を引数とする関数、戻り値とする関数を考えてみよう。voidを引数とし、それがもし純粋関数であれば、常に同じ値を返すことになる。次のような例になる。

int f44() { return 44; }

 この関数は何も取らないのではなく、既に見たように、何も取らない関数は呼びだすことが出来ない。なぜなら、何もないことを表す値が存在しないからである。この関数は何を引数に取るのか?概念的にはダミーの値を取る。それは唯一のインスタンスで、明示しなくてもよい。単位元(unit)の各関数はtarget typeからある一つの要素を取り出すすることと同値である。(ここでは、int 44を取り出している)実際にf44を数字44の別の表現として考えることが出来る。これは関数(矢印)ついて述べることで集合の要素について述べること代わりになることの例である。単位元から任意の型Aへの関数は集合Aの要素と1対1で対応している。
 戻り値がvoidである関数はどうだろう?単位元を戻す純粋関数は何もしない。議論の対象から外す。
 数学的に、集合Aからシングルトン集合への関数はAの各要素をシングルトン集合の唯一つの要素へ写す。Aに対して唯一つこのような関数が存在する。これは次のような関数である。

void fInt(int x) { return; }

 任意の数値を与えると、voidを返す。
 任意の型に対して同じ式を実装した関数はパラメータ多相と呼ぶ。具体的な型の代わりに型パラメータを使ってこのような関数を実装することができる。任意の型から単位元への多相な関数をunitと呼ぶことにする。

<T> void unit(T x) { return; }

 次に2つの要素を持つ集合である。JavaではBooleanである。Booleanからの純粋関数はそのような集合の2つの値を取り出すが、一方はTrueと対応し、もう一方はFalseと対応する。
 Booleanへの関数は述語と呼ばれる。JavaではCharacter.isDigitなどが述語に該当する。