先日公理系について書かれた良記事を見つけた。
自分は数学系ではないが、インスピレーションを受けたので、まとめてみたい。
公理とは
議論を始めるにあたって仮定することをまとめたもの。
数学では議論を始めるにあたって、「議論の対象は公理系を満たすものだけである」ということを宣言し、その公理系から論理によって対象の持つ性質や理論を導く。
例えば線形代数学では、ベクトル空間の公理を満たすものをベクトルと定義し、ベクトル空間の公理からベクトルの性質やその集合であるベクトル空間の構造を調べる。
ここで重要なのは、公理さえ満たしていればその実体はどのように構成されていてもよいということだ。ベクトル空間の公理さえ満たしていれば、理論を適用したい対象が幾何ベクトルだろうが数ベクトルだろうが、線形代数の理論は適用できる。
また、逆に公理をきっちり定めておくことで、公理を満たしていないものについての言及を排除することができる。ベクトル空間の話をしているときに突然非線形性の話を持ち込まれたとしても「いやいやそういう話は今していないから」と退けることができる。
プログラミングで公理に当たるもの
この考え方、実はプログラミングにも使われている。ポリモーフィズムで使われるインターフェース(抽象クラス)である。
オブジェクト指向プログラミングでは、
- 抽象クラスによってインスタンスの持つべきメソッドやメンバを規定し、その前提で上位のコードを組む
- クラスを実装するときにその抽象クラスを継承させ、メソッドやメンバを実装する
という手法がとられる。こうすることで、上位コードを組む時にインスタンスの実装や内部構造を気にすることなくコーディングすることができる。これをカプセル化という。また、カプセル化によって、クラスの実装を追加したり変更することになっても、インターフェースを満たしてさえいれば上位コードから問題なく呼び出すことができる。
そう、ここで言うインターフェースこそ公理そのものなのである。
例えば、ベクトルを使ったコードをイチから作りたいとする。C++でベクトルのような構造を使うとしたら、データの保持には1次元固定長配列、動的配列と生ポインタ、std::vector、std::array、動的配列とスマートポインタなど複数の方法が考えられる。とりあえず1個決めて実装したとしても、「パフォーマンスが実はほかのものがよかった」とか「コードの移植性を考えるともっと他のものがよかった」とか「スマートポインタを使おうとしたら移植先がC++03とかだった」といった理由でほかの実装に変更する必要があるかもしれない。
そこで、ベクトルの抽象クラスを定義し、上位コードはその抽象クラスを使って組む。ベクトルの抽象クラスIVector
を定義し、メンバ関数にスカラー倍とか内積、次元のゲッタなどを定義しておく。これが「ベクトル空間の公理を仮定すること」に対応する。
そしてベクトルを実装するとき、IVector
を継承させる。スカラー倍や内積、次元のゲッタなどを、採用した内部構造に即した形で実装する。上位コードからはIVector
に定義された方法以外ではアクセスされないことが保障されているので、内部実装は好きに組むことができるのである。
オブジェクト指向の数学的根拠
しばしば「オブジェクト指向には数学的な根拠がない」という主張がある。しかしそういった主張は必ずしも正しくない。なぜなら、上述のようにオブジェクト指向の本質には公理的数学論が自然な形で含まれているからだ。
しかし、それはオブジェクト指向を正しく理解した人が書いた場合である。オブジェクト指向と言われる言語では、理解していない人が継承などをめちゃくちゃに作ったコードを書くこともできる場合が多い。なので「実際のところ、世の中には数学的根拠のないOOPコードが溢れかえっている」という批判もある。
ただしそれはOOPをわかっていない人が書いた場合の話で、OOPの本質に数学的根拠がないというのは間違いである(「正しくないコードを書くことができてしまう」というのがプログラミング言語としては悪いことなのだが...)。オブジェクト指向コードを書くときは、ちゃんと理解して書くようにしたい。