抽象的なことは分かりにくいとか扱いが難しいという話はよく聞きます。しかし、抽象的じゃないがゆえに煩雑だったり余計な手間がかかることもしばしばあります。
「名前の削減問題からインスティチューションへ」で出した掛け算の法則
- mult(unit, x) == x
- mult(x, unit) == x
- mult(mult(x, y), z) == mult(x, mult(y, z))
これはモノイドの公理系です。集合ベースのモノイド(普通のモノイドです)は、集合Mと2つの関数(写像)からなる代数的構造です。2つの関数は次の形です。
- u:1→M
- m:M×M→M
ここで出てきた記号 M, 1, u, m の使い方は次のようなものです。
- 「M」は、なんらかの集合を表す。特定の集合を意味してない。「M」は集合を表す変数記号である。
- 「1」は、単元集合を表している。1 = {0} のように事前に合意されているとする。「1」は特定の集合を表す定数記号である。
- 「u」は、特定の集合1から、なんらかの集合Mへのなんらかの関数を表す。特定の関数を意味してない。「u」は関数を表す変数記号である。
- 「m」は、なんらかの集合Mの直積M×Mから、なんらかの集合Mへのなんらかの関数を表す。特定の関数を意味してない。「m」は関数を表す変数記号である。
モノイドという概念は、特定の集合や特定の関数に縛られたものではなくて、一般的・抽象的なものです。一般的・抽象的に記述するのは、定数記号(ここでは1)だけでなくて、集合変数(なんらかの集合を表す記号)と関数変数(なんらかの関数を表す記号)が必要なのです。
高階の型や高階の関数を持たないプログラミング言語だと、集合変数や関数変数は持てません。関数や型がデータとして扱えても、その構文が抽象的な構造を記述するのに向いているとは限りません。
先に出した「掛け算の法則」に対応する「足し算の法則」は次のようです。
- add(zero, x) == x
- add(x, zero) == x
- add(add(x, y), z) == add(x, add(y, z))
掛け算の法則と足し算の法則は、次のモノイドの法則の2つのインスタンスです。
- m(u, x) == x
- m(x, u) == x
- m(m(x, y), z) == m(x, m(y, z))
「u → unit、m → mult」と具体化すれば掛け算の法則、「u → zero、m → add」と具体化すれば足し算の法則になります。
集合変数や関数変数が使えないと、抽象的な記述の代わりに何か典型的な具体例を出して、その具体例に出現する定数記号を置き換えて他の具体例を導出することになります。「コピペ+置換操作」です。定数記号(具体的な名前)を、あたかも変数記号のように扱うことになります。変数の具体化と系統的な定数の置き換えは、ある状況下では同じ効果が得られます。が、「コピペ+置換操作」じゃウンザリします。
複数の具体物を単一記号で表す変数の導入は、抽象度を上げることになります。たしかに分かりにくくなったり、扱いが難しくなったりもするでしょう。しかし、異なるたくさんのモノが共通性を持つとき、その共通性を抽出して記述するには抽象化と変数記号を使うほうがはるかに簡潔になります。たくさんの個物のあいだに「アレとコレは似ている」という個別関係を列挙するのは大変です。列挙しきれなくて曖昧にお茶を濁すこともあります。「コピペ+置換操作」で類似物を作り出すのもアンマリです。
思い切って抽象の階梯を登るほうが楽なときもあります。