Dartの演算子オーバーロードを使ってみる

たまにはブログも更新しないと、ということで。以前Flashで作ったものをDart+HTML5 Canvasに移植したものです。ドラッグでポイントを動かせます(Operaで動かないのは……なんででしょう?)。リンク先にソースコードも置いてあります。
これはもともと、ActionScriptに演算子オーバーロードがなく、ベクトルの計算などが書きづらいのをどうしようかと試行錯誤したものだったんですが、例えばpositionとvelocityがVector2クラスのインスタンスだとして、
position = add(position, mul(velocity, deltaTime));
とでもするしかないかなという感じでした(詳しくは省きますが、実際にはこれじゃダメでしょう)。DartならVector2クラスにoperatorを定義すれば、
position += velocity * deltaTime;
のように直感的に書けます。
演算子オーバーロードというと、そんなもの要らないよと言う人も多いのですが、ベクトルや行列の計算にはあるととても便利なのです。ゲームプログラム(とりわけ3Dの)でC++が使われる理由のひとつとして、演算子オーバーロードで幾何学計算が素直に書けて、しかもコンパイラの最適化で十分に速いアセンブリコードが出力されるというのが大きいと思います。UnityのJavaScriptも、ベクトル等の計算が普通に書けるよう独自に拡張されています。
そんなわけで、Dartに演算子オーバーロードが入ってきたのは嬉しいです。……が、上の例がfrogcでJavaScriptにどう変換されるか見てみると……いちいち型チェックしてるんですか、これ。オーバーヘッドがかなり大きそうで不安w DartネイティブのVMに期待って感じでしょうか。
position = $add$(position, ($mul$(velocity, deltaTime)));
...
function $add$complex$(x, y) {
if (typeof(x) == 'number') {
$throw(new IllegalArgumentException(y));
} else if (typeof(x) == 'string') {
var str = (y == null) ? 'null' : y.toString();
if (typeof(str) != 'string') {
throw new Error("calling toString() on right hand operand of operator " +
"+ did not return a String");
}
return x + str;
} else if (typeof(x) == 'object') {
return x.$add(y);
} else {
$throw(new NoSuchMethodException(x, "operator +", [y]));
}
}
function $add$(x, y) {
if (typeof(x) == 'number' && typeof(y) == 'number') return x + y;
return $add$complex$(x, y);
}
...
ついでに、演算子オーバーロードは必要なんだ、という他の方の証言を。
JavaScriptになかなかつかない理由。いずれはなんとかなりそう?
余談ですが、DartといえばHello, WorldをJavaScriptに変換したら17000行になったというのが話題になりましたけど、今はだいぶ小さくなってます。このサンプルの出力(CanvasTest.dart.js)が150KBくらいです。Closure Compilerを通すと55KBくらいになるんですが、動かなくなるので、ちょっと調べないといけなさそうです。
(追記)ちなみにCoffeeScriptでも演算子オーバーロードの要望が何度も挙がって都度却下されているようなのですが、どうも、動的型言語からJavaScriptへのトランスレートでは非効率な変換結果にならざるを得ないようです。a + bという式があったときに、aとbの型が実行時まで決まらないので、関数の呼び出しに変換するべきかどうか分からないと……。
C#からJavaScriptに変換するJSILのRaytracer Demoを見ると、演算子オーバーロードが素直に変換できているので気になります。ただ、こちらは出力が結構大きくなってしまうようですが。
HTML5ゲームプログラミング 2012年4月2日
