「代数」という言葉は様々な意味で用いられます。ここでは、余代数とペアにして語られるアレのことです。と言っても何だかよく分からんでしょうから以下で説明します。代数・余代数の定義には圏論を使うのが普通ですが、ここでは圏論なしでいきます。クラス・余クラスは、代数・余代数を裏返しただけで事実上同じものです。
内容:
演算と代数
Aをなんらかの集合だとして、f:A×A→A という関数(写像)をA上の二項演算と呼びます。A = Integer(整数の集合)のとき、足し算や掛け算は二項演算となります。add:Integer×Integer→Integer、mult:Integer×Integer→Integer とかですね。
二項だけでなく、三項や一項の演算もあります。三項演算というと、プログラマの人は、(- ? - : -) を思い浮かべるかもしれません。(b, n, m)|→(b ? n : m) という対応は、Boolean×Integer×Integer→Integer という三項演算となっています。しかし、この演算に限らず、3つの引数を取る関数は何であれ三項演算と呼べます。整数を二乗する関数 n|→n*n は一項演算(単項演算)の例です。
零項演算は、引数なしの関数のことです。引数なしの(副作用もない)関数は定数とみなしていいので、零項演算とは定数だとも言えます。以下では、零項演算は集合Voidからの関数だとします。Voidとは、単元集合(シングルトンセット)です。空集合じゃないので注意してください。Voidの唯一の要素はなんでもいいので星印(*)とかドット(・)とかで表されることが多いですね。でも、星印を掛け算と混同したりするのが嫌なので、it という名前を付けておきます。Void = {it} です。
集合Aに対して、演算をいくつか一緒に考えたものを代数と呼びます。Aをその台集合(underlying set)といいます。ここで、演算の項数(アリティ)は自由に選べますが、演算の値は必ずAに入らなくてはなりません。つまり、代数の演算は、(なにか)→A という形をしてます。実例を挙げましょう。先ほど既に出した足し算と掛け算です。
- add:Integer×Integer→Integer
- mult:Integer×Integer→Integer
- zero:Void→Integer
- one:Void→Integer
零項演算(=定数)のzeroとoneも入れました。
代数の指標とモデル
集合A上の代数(台集合がAである代数)を記述するのに次の方法を使うことにします。
- 代数の名前を決める。
- 台集合を明記する。
- 演算を並べる。
- 演算の引数仕様だけ書いて、値に関しては書かない(決まっているので省略する)
- 集合の直積はカンマ(,)で表す。
具体的には次のようになります。
algebra Arith(Integer) { add: Integer, Integer; mult: Integer, Integer; zero: Void; one: Void; }
この例では:
- 代数の名前はArithである。
- 台集合はIntegerである。
- 演算は、add, mult, zero, oneである。
今書いたのは代数の仕様であって、実装ではありません。add, multなど演算の名前から、足し算、掛け算を暗示していますが、あくまでそれは暗示であり、ほんとの足し算/掛け算である保証はありません。演算の名前や記号が、実際の関数に割り当てられてはじめて“ほんとの代数”が決まります。
Arithのような代数の仕様を指標と呼びます。指標はインターフェイスと同じことです。指標に対する“ほんとの代数”は、モデルとかインスタンスと呼びます。「インスタンス」は台集合の要素と混同しそうなので、モデルを使います。我々がよく知っている足し算と掛け算は、上に示した指標のモデルです。
指標Arithでは、台集合がIntegerと具体的に指定されていました。台集合も未定であるなら、集合を表す変数を使って指標を書くことになります。
algebra Arith(X) { add: X, X; mult: X, X; zero: Void; one: Void; }
台集合を表す変数Xをソートと呼びます。 この例はソートをひとつしか持ちませんが、複数のソートを持つ指標も考えられます。
余演算と余代数
代数の演算は、(なにか)→A という形をしてました。一方、A→(なにか) の形をしている関数をA上の余演算と呼びます。例えば、n|→(n, n) とコピーしてペアを作る操作は、Integer→Integer×Integer の形をしているので余演算です。値の形が Integer×Integer なので、余二項余演算といえます。
台集合といくつかの余演算の組み合わせが余代数です。説明はこれだけにして、余代数の指標の例を挙げてしまいましょう。
coalgebra CoMonoid(Integer) { dup: Integer, Integer; dispose: Void; }
これは指標なので、“ほんとの余代数”ではありませんが、次のように定義して“ほんとの余代数”=“余代数指標のモデル”が得られます。
- dup(n) = (n, n)
- dispose(n) = it
この例で、dupはInteger上の余二項余演算、disposeは余零項余演算です。dupとdisposeの組は、結合律の双対である余結合律、(左右の)単位律の双対である余単位律を満たします。つまり、dupを余積(余乗法)、dispoaseを余単位とする余モノイド(コモノイド)になっています。
次のCounterは、余代数の指標の例ですが、余一項余演算は一項演算でもあるので、代数とみなすこともできます。(この点は次節で詳しく触れます。)
coalgebra Counter(Integer) { inc: Integer; dec: Integer; reset: Integer; }
余代数の隠蔽指標
前節の指標Counterのモデルを具体的に定義しましょう。
- inc(n) = n + 1
- dec(n) = n - 1
- reset(n) = 0
inc, dec, resetは、いずれも余一項余演算なので、一項演算でもあり、次の代数指標のモデルとみなすこともできます。
algebra Counter(Integer) { inc: Integer; dec: Integer; reset: Integer; }
では、一項(単項)演算だけの場合は代数と余代数の区別はないのでしょうか? そうとも言えないのです。ドライに集合と関数(写像)だけで考えると区別はありませんが、余代数という場合、台集合は「隠蔽された状態の集合」と考える場合が多いのです。「隠蔽された状態」をHiddenと書くことにして、Counter余代数の指標は次のようです。
coalgebra Counter(Hidden) { inc: Hidden; dec: Hidden; reset: Hidden; }
隠蔽された状態Hiddenは、直接観測することはできません。inc, dec, resetなどを実行しても、何がどうなったのかはユーザーから知る手段がありません。それでは困るので、観測用の余演算(観測子)を設けます。
coalgebra Counter(Hidden) { value: Integer; inc: Hidden; dec: Hidden; reset: Hidden; }
台集合が隠蔽された状態である余代数は、オブジェクト指向のクラスだと考えることができます。Hiddenがthisに相当します。thisの正体が何であるかは隠蔽されていますし、メソッドの引数や戻り値にthisを明示的には書きません。通常のインターフェイス記法でCounterの指標を書いてみると:
interface Counter { Integer value(); Void inc(); Void dec(); Void reset(); }
ここで、incとdecの引数として、増減の差分を渡していいとしましょう。次のように変更されます。
interface Counter { Integer value(); Void inc(Integer delta); Void dec(Integer delta); Void reset(); }
特に問題はありません。ところが、余代数の指標では、余演算は 台集合→(なにか) の形を要求されます。この場合は、Hidden→(なにか) です。inc: Hidden×Integer→Hideen は許されないのです。この問題は、カリー化で解決できます。集合XからYへの関数全体からなる集合を [X->Y] と書くことにして、Hidden×Integer→Hideen は、Hidden→[Integer->Hideen] に直せます(これがカリー化です)。このトリックを使うと、引数付きinc, decを含んだCounterの余代数指標は次のようになります。
coalgebra Counter(Hidden) { value: Integer; inc: [Integer->Hidden]; dec: [Integer->Hidden]; reset: Hidden; }
クラスと余クラス
圏論の観点からは、オブジェクト指向のクラスは隠蔽された余代数とみなすことが多いようです。「隠蔽された」を厳密化することもできます。構文的には、指標で使う記号に「これは隠蔽されてる」と目印を付けるだけのことです。意味論としては、ひとつの指標にモデルがたくさんあって、それらが区別できないという現象で「隠蔽」が説明されます。実用上十分に一般的な条件のもとで、(隠蔽された)余代数指標に対して標準的なモデル(終余代数)が存在することが示せます。
ある観点からは、クラスは余代数と考えることができるわけです。思い切って クラス=余代数 と考えましょう。余代数は代数の双対でした(ん? 双対の説明はしてなかった)。そうすると、次のような対称な関係が成立することになります。
同じ クラス == 余代数 | | 双対| | | | 余クラス == 代数
余クラスとは代数のことです。わざわざ変な呼び方をしなくてもいいと思われそうですが、最近、クラスのひっくり返しとして代数が現れる現象に出会いました。最初にクラスがあって、それを鏡に写したような形で代数が現れるので、余クラスと呼ぶのがピッタリのような気がしたのです。