演算関数をリネーム
[mathjs]の演算関数はadd,subtracct,multiply,divide
と長くまたわかりにくい。そこで演算関数自体を代入することでリネームする。
以前の記事で書いたJavaScriptのLegendre関数を複素関数化する。
import mathjs from 'mathjs'
export default function(n, x){
const add=mathjs.add, sub=mathjs.subtract, mul=mathjs.multiply, div=mathjs.divide;
if( n===0 ) return 1;
if( n===1 ) return x;
if( x===1 ) return 1;
else if( x===-1 ) return n%2===1 ? -1 : 1;
let val=0, val_n=x, val_n1=1;
for( let i=2; i<=n; i++ ){
// val=2*x*val_n-val_n1-(x*val_n-val_n1)/i;
val=sub( sub(mul(mul(2, x), val_n), val_n1), div(sub(mul(x, val_n), val_n1),i));
val_n1=val_n;
val_n=val;
}
return val;
}
C++でいうところのusing
に近い感じである。代入なのでゼロコストではないがmath.multiply
とか書くよりよりだいぶマシだろう。
そもそも何故こんな書き方が必要なのか
JavaScriptに演算子オーバーロードがないから
遥か古には提案はされていたが見送られたらしい。
そして現代人もオーバーロードもどきを得ようと頑張っている。
みんなvalueOf
を使い(悪用)しようと足掻くがあまりうまく行かない上、言語の動作に悩まされているようだ。
結論としては構文解析をするのが一番だと言っている、まあ順当な結論に見えるが本当に可能か?
C++の演算子オーバーロードとは
演算子オーバーロードといえばC++ということでC++はどうしているかというと型を解決しています。
T operator+(const T &left, const T &right);
みたいな定義があっても演算できないクラス同士で実装しようとすればコンパイルエラーになります。
JavaScriptに戻って考えると弱い動的型付けという非常に方という概念が弱い、つまりそれぞれの型にあった処理を選べるのか?というとまず、トランスパイル処理では型を推論するのはほぼ不可能だろう。そもそも型による処理の切り分けができないJavaScriptでは同じ関数で処理を分けるのが非常に難しい。内部で型チェックをするぐらいしかアイデアがない...、すべての演算子にそういった処理を埋め込むのは現実的ではないだろう。
WebAssemblyは解決になるか?
すべての処理をC++でかけるなら解決の一つになるかもしれないがWebAPIを叩けない現状でそれは不可能だ。C++はコンパイル時に型解決できてないとならないのでwasmにする時にどういった方が使えるかは決まってしまう。それを型のゆるいJavaScriptで使おうとすると...怖い。
ブラウザでどんな言語も扱えるような印象のあるWebAssemblyだが実際は吐かれる中間のアセンブラに精通している必要がある気がする。もちろんプラクティスが溜まってある程度安全に扱えるようになれば使えるかもしれないが、まだ探り探りやっている印象である。
その他
TypeScriptは型があるので演算子オーバーロードができるかと思ったが情報は見つからなかったのでないと思われる。
JavaScriptでASTを使ってコードをインジェクションしてみるでうまい解決はできるんだろうか?
トランスパイル時に置き換えるにしても数値計算はどうしてもfor( let i=0; i<n; i++ )
の形になるのですべて置き換えるのは可読性に大きな影響を与えそうである。
結局、ちまちま頑張るのが一番だと思います。