新しい coerce の方法が思い付きました。

ちょっと dev 向けかもしれませんが、拡張ライブラリレベルで実装できるた
め、ここで言います。こちらではちゃんと動いています。

現状では演算が定義されているクラス (数、行列など。総称として 
Calculable と呼んでみます) のメソッド定義は以下のようになっていますが、

class Calc1
  def +(other)
    case other
    when Calc1
      ...
    when Numeric
      ...
    else
      x, y = other.coerce(self)
      x + y
    end
  end
  
  def coerce(other)
    case other
    when Numeric
      [ ... ]
    else
      raise TypeError, "#{type} can't be coerced into #{other.type}"
    end
  end
end

これではクラス定義を大幅に変更しなくては、数ではない Calculable を追加
することができません。行列、ベクトル、区間、自動微分なんかがそれに当た
りますね。もっとあると思いますが。上のスクリプトでは「区間型」との演算
ではエラーを起こします。

かと言って、クラス定義の中に他の Calculable の名前を含めるのは、カプセ
ル化の観点から良くないと思います。だって、新たな Calculable の定義を追
加すると、他の Calculable の定義も書き換えなければなりませんから。

「クラス定義はそのクラス同士に対する演算で閉じている」

べきなのです。そうすれば新たな Calculable の追加で他のクラスに影響を及
ぼすことはほぼ皆無に近いです。

----------------------------------------------------------------------

で、どのようにすればそれが可能か?

coerce を Calculable のスーパークラス (現状では Object ですね) で定義
し、それぞれの Calculable の優先度 (後述) とコンストラクタ (後述) を定
義すれば良いのです。

coerce でやっていることは、基本的にはどれも同じで
「相手の型を見て、どちらかの型に合わせる」
ことをやっています。優先度の低い方が型上げされるわけです。
そのため、スーパークラスで定義した方が良いのです。

Numeric の優先度を 10 として定義し、 Object::coerce を定義する C 言語
ソースです。僕が ruby で数値実験をする際、このソースから生成される 
coerce.o を真っ先に require しています。

#include "ruby.h"

ID scalar, priority;

VALUE
coerce_general(self,other)
     VALUE self,other;
{
        VALUE va,vb;
        int pa,pb;
        va = rb_funcall(self, priority, 0);
        vb = rb_funcall(other, priority, 0);
        pa = FIX2INT(va);
        pb = FIX2INT(vb);
        if (pa > pb) 
                return rb_ary_new3(2, rb_funcall(self, scalar, 1, other),self);
        else if (pa < pb)
                return rb_ary_new3(2,other, rb_funcall(other, scalar, 1, self));
        else {
                if (CLASS_OF(self) == CLASS_OF(other))
                        return rb_assoc_new(other, self);
                return rb_assoc_new(rb_Float(other), rb_Float(self));
        }
        
}
               
VALUE numeric_priority(VALUE self)
{
        return INT2FIX(10);
}


void
Init_coerce()
{
        rb_define_method(rb_cObject, "coerce_general", coerce_general, 1);
        rb_define_method(rb_cObject, "coerce", coerce_general, 1);
        rb_define_method(rb_cNumeric, "coerce", coerce_general, 1);
        rb_define_method(rb_cFloat, "coerce", coerce_general, 1);
        rb_define_method(rb_cNumeric, "priority", numeric_priority, 0);
        priority = rb_intern("priority");
        scalar = rb_intern("scalar");
}

----------------------------------------------------------------------

優先度について

それぞれの Calculable には優先度があります。 Calculable のパラメータ?
(行列では成分、複素数では実部と虚部) には、優先度の低いものしか入りま
せん。たとえば、複素数と分数において、

1 + 4i
------
2 + 3i

などと記憶すべきではなく、

「分数」 + 「分数」i

と記憶されるべきです。この場合、「複素数の方が分数よりも優先度が高い」
と言います。

==注意==
「優先度がちゃんと定義されていないと、複数の Calculable の間の演算がちゃ
んと行われません!!!!!」

優先度の定義は、 priority という優先度をあらわす Fixnum を返すメソッド
を定義します。たとえば、

class Matrix
  def priority
    200
  end
end

とします。こちらで定義している(する予定の)優先度は

200 行列、ベクトル
100 自動微分
 20 区間
 18 複素数
 15 分数
 10 ほかの Numeric

です。優先度が気に入らないときは、 priority メソッドを再定義するだけで
良いので、お手軽だと思います。

----------------------------------------------------------------------

コンストラクタ

優先度を定義した後、必要なのは、
「優先度の低いものを自分の型に型上げする」
ことです。優先度の低いものは、高い方から見ると「スカラー」に見えます。
コンストラクタの定義とは、乗法単位元 (e * x == x * e == x, for all x を
みたす元 e、行列なら単位行列に相当)のスカラー倍を定義することです。

コンストラクタの定義は、 scalar というメソッドを定義します。たとえば 
Matrix ならば、

class Matrix
  def scalar(scalar)
    Matrix.scalar(row_size, scalar)
  end
end

となります。クラスメソッドではなく、インスタンスメソッドです。なぜなら、
次元などの情報が必要になることがあるからです。

----------------------------------------------------------------------

結果

上の方法を用いると、演算の定義がとても簡単になります。
また、他の Calculable との演算もちゃんと動作するようになります。
さらに、Calculable の定義の度に coerce を定義する必要もなくなります。

class Calc1
  def +(other)
    if other.kind_of?(Calc1) then
      「Calc1 型同士の加算」
    else
      x, y = other.coerce(self)
      x + y
    end
  end
end

======================================================================

こちらではまだ試していませんが、上の考えをもう一歩進めると、 
Calculable のクラス Calculable を定義したくなります。

そうすることで、演算の定義がもっと簡単になると思います。

class Calculable
  def +(other)
    if self.type == other.type then
      self.plus(other)
    else
      x, y = other.coerce(self)
      x + y
    end
  end

  def coerce(other)
    ...
  end
end

class Matrix < Calculable
  def plus(other)
    「行列同士の加算」
  end
end

class Numeric < Calculable
  ...
end

======================================================================

こちらでは、 matrix.rb を改良したものを拡張ライブラリに焼き直した 
matrix.o 、Float で区間演算を行う interval.o 、自動微分クラス 
auto-differencial.rb がありますが、
priority & scalar scheme で「正しく」動作しています。

意見、反論などは歓迎します。

--
Takashi Nishimoto: g96p0935 / mse.waseda.ac.jp
I love Emacs, zsh, and Linux!!
See ftp://ftp.misao.gr.jp/pub/tak/README
Key fingerprint = EE D7 1D 18 A9 42 C0 8A  63 E7 F2 AC 4D C7 83 6B