Dec 31, 2007

coerceとか代入演算子とか


数学関数をお題にして、拡張ライブラリの制作方法を勉強中。以下、今日知っ たことのメモです。昨日の分と同様、RVec4は「4次元ベクトルを表すクラス」 として読んでください。

coerce

たとえば v * 2.0 と書いたなら RVec4#* が利用されるが、 2.0 * v だとまず Float#* が使われる。しかし Float は RVec4 というクラスについて知らない。
このような場合、Ruby では v.coerce(self) というメソッドを介して RVec4 に自身の型変換を依頼し、その結果を元に演算を続行する:
a, b = v.coerce(self)
a * b
自分のクラスで coerce を導入するならば、
rb_define_method( rb_cRVec4, "coerce", RVec4_coerce, -1 );
…のように初期化して、相手方に応じて適切な結果を配列で返す:
static VALUE RVec4_coerce( int argc, VALUE* argv, VALUE self )
{
...
switch( TYPE(argv[0]) )
{
case T_FLOAT:
case T_FIXNUM:
case T_BIGNUM:
    /* RVec4 (op) argv[0] の形式で再計算を試みる */
    return rb_ary_new3( 2, self, argv[0] );
...
計算中に何度もこのような型変換が発生してしまうのはできる限り避けたいと ころ。かといって coerce を正しく書いておかないと、無意味な式が通ってし まう可能性がありますね( "hoge"+vとか書かれたら? )。無用な coerce がなるべく発生しないように気を付けるしかないのかもしれません。
参考:

代入演算子

@を適当な演算子だとすると、
式1 @= 式2
という式は
式1 = 式1 @ 式2
として扱われる。このため、たとえば
rb_define_method( rb_cRVec4, "+",  RVec4_op_binary_plus, -1 );
rb_define_method( rb_cRVec4, "+=", RVec4_op_assign_plus, -1 );
のように初期化していたとしても、 v1 += v2 という記述で RVec4_op_assign_plus が利用されることはない。
上記の RVec4#+ も該当しますが、元となる2項演算子がオブジェクトを 生成するようなものだとすると、実行時のパフォーマンスは悲惨なものになり そうですね。代入演算子は利用せずに、別な名前で明示的に利用することにな るようです。
Yoshiさんによる math3d では メソッドが破壊的であることを示す 記号を使って、
void
Init_math3d()
{
...
  rb_define_method(cVector, "add!", rb_vec_add, 1);
…と、わかりやすい名前が付けられていました。
参考: