(2003/03/03 12:51:35 PST) <rational>クラスを外付けで実現するうまい方法を考えているうちに、 現在のGaucheの数のクラス階層に疑問を持ったのだった。
今のところ(0.6.7.1)、Gaucheは次のようなクラス階層を持っている。
<number> → <complex> → <real> → <integer>
ここで「A→B」はBがAを継承していることを示す。 このクラス階層により、数値クラスは下のように予想される挙動を示す。
(class-of 4+2i) => #<class <complex>> (is-a? 4+2i <real>) => #f (class-of 3.14) => #<class <real>> (is-a? 3.14 <complex>) => #t (class-of 3) => #<class <integer>> (is-a? 3 <real>) => #t
これらの数値は次のように実装されている。
クラス | 実装 |
<number> | (実装無し。 抽象クラス) |
<complex> | ScmComplex {double real; double imag} |
<real> | ScmFlonum { double val; } |
<integer> | ScmBignum または即値 |
しかし、このクラス階層が実装継承になっていないことがずっと気になっていた。 <complex>にrealとimagのスロットがある、と考えた場合、それを継承している <real>や<integer>にそれらスロットも継承されなければおかしいはず。 実装上は数値のクラス階層はCのレベルでハードワイアドされているので こんな妙な継承が可能になっているが、他の部分との整合性がない。
nobsunのやってるHaskell MLで、 HaskellにおけるNum、Fractional、Floatingといった「クラス」は 可能な操作の集合によって決められるインタフェースのようなもので、 実装を決めているComplex, Rational, Integerといった「データタイプ」とは 分離しているのだということを知ってなるほどと思った。 今のGaucheの実装の混乱はインタフェース継承と実装継承を 混同していたゆえのことだったのか。
GaucheのOOP (というかCLOS)では特にシステムはインタフェース継承と 実装継承の区別を強制しない。プログラマが意識して「このクラスは インタフェース用」「このクラスは実装継承用」と書きわける。 (それをせずに無節操に多重継承を使うと何がなんだかわからなくなるので、 この自由度は諸刃の剣だ)。
そこで、<number>, <complex>, <real>, <integer>各クラスは mixinのインタフェースクラスということにして、 それぞれを実装するクラスを枝分かれさせて定義することにしよう。
<number> ↓ <complex> → <complexnum> ↓ <real> → <flonum> ↓ <rational> ↓ <integer> → <bignum>, <fixnum>
この時、ついでにインタフェース継承の方に<rational>クラスを入れといて、 その実装は例えばgauche.rationalをuseすれば提供される、ということに しとけばすっきりまとまる。
Tag: 数値