動機
python にはnumpyというライブラリがあります。numpyでは行列の演算を以下のコードのように処理できます。
a = numpy.array([1,2,3,4,5])
b = numpy.array([1,2,3,4,5])
a * 3
=> [ 3 6 9 12 15]
a - b
=> [0 0 0 0 0]
a * b
=> [ 1 4 9 16 25]
でこんな感じの演算をJavascriptで実現したいなぁと思いました。
結論
いきなりですが結論から言うと無理に等しいです。そもそもJavascriptは演算子のオーバーロードを許していません。
ただvalueOfを継承することでイイ線まではいきます。
参考: http://www.2ality.com/2011/12/fake-operator-overloading.html
valueOfでいいところまで実装してみる
valueOfはJavascriptのObjectに実装されてるメソッドです。つまり全てのオブジェクトがvalueOfを継承しています。プリミティブには無い。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
こんな事が出来ます。
var exampleOne = {valueOf: function(){return 1;}};
var exampleTwo = {valueOf: function(){return 2;}};
exampleOne + exampleTwo
=> 3
なんか行けそう!!なきがしますが、結局valueOfはプリミティブな値を返さないといけません。
ということは結局振り出しに戻ります。
var exampleOne = {
valueOf: function(){
return {
add: function ・・・ ← このように出来ない
};
}
};
なかなかうまく行きません。。。そこで次の方法を考えます
式全体をラップする
式全体をラップすることで 部分的 に扱うことが出来ます。
参考: https://github.com/rauschma/op_overload
var operands = []
fake_numpy.prototype = funciton(){
operands.push(this);
return 3;
}
function A(value){
//__minus などの定義省略
var operator;
var ops = operands;
if(value === 0 && ops.length === 2){
operator = __minus
}else if (ops.length === 2 && value === 1) {
operator = __divide;
} else if (ops.length >= 2 && (value === 3 * ops.length)) {
operator = __add;
} else if (ops.length >= 2 && (value === Math.pow(3, ops.length))) {
operator = __multiply;
}else {
throw Error("失敗");
}
operands = [];
return operator.apply(this, ops);
}
valueOfが評価されるときに一旦オブジェクトを保存して、ラップした関数内で初めての計算を行います。
例えばvalue が式の項目の3の倍数の数であれば、元の式は恐らくすべて+なんだろうなぁと推測できます。
それらの数値から元の式の形を判定しそれら全てを特定の関数(例: __add など)に突っ込みます。
しかしこれでは複雑な式は扱えません。。。 複数の演算子があるともはや検知不可能だからです。。。。
var f1 = fake_numpy([1,2,3,4]);
var f2 = fake_numpy([1,2,3,4]);
var res = A(f1 + f2)
res.array
=> [2, 4, 6, 8]
var res = A(f1 + f2 - f1) ← これだけで破綻する
var res = A(f1 + f2 + f1) ← これならOK
ではどうするか・・・
構文解析器を使用する
こちらの方法はもはや純javascriptではありません。javascriptを抽象構文木に分解して、新たにjavascriptのコードを作ります。JSのコードを動的に変換するイメージ。
ちなみにPaper.jsではこの手法が取られています。
githubからソースを抽出します。
var binaryOperators = {
// The hidden math methods are to be injected specifically, see below.
'+': '__add',
'-': '__subtract',
'*': '__multiply',
'/': '__divide',
'%': '__modulo',
// Use the real equals.
'==': 'equals',
'!=': 'equals'
};
//省略
function __$__(left, operator, right) {
var handler = binaryOperators[operator];
if (left && left[handler]) {
var res = left[handler](right);
return operator === '!=' ? !res : res;
}
switch (operator) {
case '+': return left + right;
case '-': return left - right;
case '*': return left * right;
case '/': return left / right;
case '%': return left % right;
case '==': return left == right;
case '!=': return left != right;
}
}
上記の様な関数を用意して、全演算子を__$()
の関数に置き換えています。出力されるJSは
1 + 2 → __$__(1, 2)
の様な形になります。
ここまでしてしまえばなんとか出来そうです。
まとめ
純粋なJavascriptでは演算子をオーバーロード出来ません。
素直にnew numpy([1,2,3,4]).add(new numpy([1,2,3,4]))
と書くしかないです。
特殊なケースを除いてJavascriptでnumpyの様な演算をすることを諦めましょう!!
参考:http://tkengo.github.io/blog/2015/06/30/operator-overload-in-javascript/