クラス継承による自然数の構成
配列は甘え。
概要
ノイマンによる自然数の構成は「全ては集合である」という立場から自然数を具体的に与える手続きを示したものだ。となれば「全てはオブジェクトである」を標榜する Pythonian としては抽象的なオブジェクトとその機能だけを使って自然数を構成してみたくなる。ただしZF公理系に当てはめようとすると無理が生じるのでその辺は簡単に避けつつ、後者関数を継承で実現できるようなクラスを考える。
公理的集合論からの視点
クラスは人間種、インスタンスは個人等と説明されるように、クラスとインスタンスは集合と要素に似た構造を持つ。ではクラスを上手く定義することで公理的集合論を実現できるんじゃないかと思ってしまうが、そう簡単には行かない。公理的集合論が空集合を起点としてそれを「拡大」するように集合を与えていくのに対し、プログラミングのクラスはサブクラス、インスタンスを取り出して「縮小」する方向に構成する差のためだ。
要するにオブジェクト=要素 x
が与えられた時、公理的集合論では x
が属する集合を生成することができるが、プログラミングでは x
のクラスが x
より先に存在していないといけない。キャストで処理できないこともないが、その辺は実装と仕様が面倒なので次回以降に回す。
方針
出来るだけ「原始的な」機能を使って実装したい*1。自然数の構成には後者関数 による「隣り合う数」の表現が必要だが、最も単純に実現する方法はクラス継承だろう。つまり
- 適当なクラスを作り とする、
- クラスを継承して を作る、
- 作られた に対してそれを継承した を作る、
という手続きだ。
ただしこのままでは後者の数が一意に定まらないので*2、__eq__
をオーバーロードして等号判定を再定義する必要がある。ここでは の個々の数自体がクラスになるのでメタクラスでそれを実現する。 なので前者=基底クラスの比較結果を再帰的に取り出すようにすれば、 かどうかの判定問題に帰着する。 の判定も の基底クラスを適当に固定しておけば変に処理を増やさなくて済む。なお、後述の大小比較を先に実装しておけば による一般形が使えて本記事でもそうしている。
ついでに後者関数、前者関数、大小比較、各種演算もメタクラスメソッドとして定義しておくといいかもしれない。int
型への変換関数があってもいろいろと便利だろう。どれも に対する処理を記述して後者または前者を使った再帰的定義をすればいい。
実装
実装を示す。たまに「a+b
は a.__add__(b)
と等価」等と言われることもあるが正確には cls.__add__(a, b)
(cls = a.__class__
)と等価になる。a
がクラスの場合、a.__add__
はインスタンスメソッドではなくクラスメソッドとして呼ばれるので、例えば下の __le__
の再帰部分を return self.pre().__le__(x.pre())
とすると引数が足りないと怒られる。
class PythonianNumber(type): # 0 の基底クラス base = object def __new__(cls, name): return super().__new__(cls, name, (PythonianNumber.base, ), {}) # 0 判定 def is_zero(self): return self.__base__ is PythonianNumber.base # self を継承した PythonianNumber クラスインスタンスを返す後者関数 def suc(self, name=None): if name is None: name = 'Suc'+self.__name__ return super().__new__(self.__class__, name, (self, ), {}) # 基底クラスを返す前者関数 def pre(self): if self.is_zero(): raise ValueError() return self.__base__ # 大小比較は1つ書けばあとは等号判定も含め一般形で実装できる def __le__(self, x): if self.is_zero(): return True if x.is_zero(): return False return PythonianNumber.__le__(self.pre(), x.pre()) def __ge__(self, x): return PythonianNumber.__le__(x, self) def __lt__(self, x): return not PythonianNumber.__le__(x, self) def __gt__(self, x): return not PythonianNumber.__le__(self, x) def __eq__(self, x): return PythonianNumber.__le__(self, x) and PythonianNumber.__le__(x, self) def __ne__(self, x): return not PythonianNumber.__eq__(self, x) # 和積演算 def __add__(self, x): if x.is_zero(): return self return PythonianNumber.__add__(self, x.pre()).suc() def __mul__(self, x): if x.is_zero(): return x return PythonianNumber.__add__(PythonianNumber.__mul__(self, x.pre()), self) # int への変換 def __int__(self): if self.is_zero(): return 0 return int(self.pre())+1
続いて動作確認。別々にインスタンス化した から生成した自然数同士でも比較・演算できる。
zero = PythonianNumber('Zero') one = zero.suc('One') two = one.suc('Two') three = two.suc('Three') rei = PythonianNumber('Rei') ichi = rei.suc('Ichi') ni = ichi.suc('Ni') san = ni.suc('San') # True False True print(two<=san, three<=ni, three<=san) # False True False print(two>san, three>ni, three>san) # False False True print(two==san, three==ni, three==san) # 5 5 6 print(int(two+san), int(three+ni), int(three+san)) # 6 6 9 print(int(two*san), int(three*ni), int(three*san))