メンバネームスペースの応用

ActionScript3では、名前空間の機能をpublic、privateなどの可触性に応用していました。Xtalでは可触性には応用しないのですが、「2項演算子」に応用します。

2項演算子は困り者

静的な型を持たない言語にとって、+や*などの2項演算子は困り者です。
a + bという演算を見てみましょう。
aがInt、bがIntの場合、結果はIntです。
しかし、aがInt、bがFloatの場合、結果はFloatにする必要があります。*1
つまり相手の型によって、演算を変えなければなりません。

これを単純に解決すると、次のようになります。

Int::op_add: method(other){
  if(other is Int){
    return /* Intを返す */
  }
  else if(other is Float){
    return /* Floatを返す */
  }

  throw "unknown";
}

これは、将来Intと演算したい他の型が出てきたとき死にます。IntとFloat以外には対応できないコードです。

Xtalでの、従来の解決方法

Xtalでは、次のように解決していました。

Int::op_add: method(other){
  return other.op_add_r_Int(this);
}

Int::op_add_r_Int: method(other){
  return /* Intを返す */
}

Float::op_add_r_Int: method(other){
  return /* Floatを返す */
}

一言で説明すると、ダブルディスパッチをしていた、ということです。これなら、op_add_r_Intというメソッドを追加すれば、どんなクラスでも演算が可能となります。

Xtalのこれからの解決方法

これはこれでまぁいい方法だと思っていたのですが、今回追加したメンバネームスペースをこれに応用して、もっと楽にやることにしました。
a + bという演算は、a.op_add#(b.class)(b) という意味となり、次のようなコードで各型との2項演算子の定義が出来るようになります。

// Intとの演算
Int::op_add#Int: method(other){
  return /* Intを返す*/
}

// Floatとの演算
Int::op_add#Float: method(other){
  return /* Floatを返す*/
}

// 2次元ベクトル
Vector2D::op_mul#Float: method(other){
  return Vector3D(this.x*other, this.y*other);
}

*1:強制的に左の型にあわせる、という手もあることはありますが、C/C++経験者の誰もが、そんなの嫌だ、と言うでしょう