JavaScriptには演算子オーバーロードの機能がありません。
過去にECMAScript4のDraftに'Operator overloading'が記載されていたらしいのですが、
現在に至るまで採用されていません。
複素数や多倍長精度の計算が,+-*/で四則演算出来ればうれしいですよね!。
JavaScriptで演算子オーバーロードを実装してみました。
下のリンクはスピーカーとキャビネットのシミュレーションをしている例です。
ただしChromeで。IE11は現在のECMA仕様に対応していないので実行不可です。
使用例 スピーカーキャビネットシミュレータ
普通の四則演算に見える部分で複素数を使用しています。
このロジックは、gnuplotではシンプルに書けるのですが、JavaScriptやJavaでは
複素数は全て関数で計算しなければならず、だいぶ面倒です。
複素数計算はmath.jsを使用しています。
演算子関数はオブジェクトのprototypeに登録すれば良さそうです。
これで、クラスのインスタンスで登録した関数が参照出来るようになります。
ClassX.prototype[ "+" ] = function(L,R){ return L.add(R); }
演算子を関数呼び出しの形に変換しておいて
L + R; ↓ op("+", L, R );
こんなかんじで呼び出す
function op(opType, L, R){ return L[ opType ]( L, R); }
で、変換したコードをeval()で実行すればいけそうです。
実際には、素の演算を行う場合とか、
短絡評価の場合とかで、もうちょい複雑になります。
JavaScriptの構文解析には、既存のパーサがいくつもあり、ASTという標準の構造があります。
これらを利用すれば自分で構文解析する必要はありません。
結果、思ったより簡単に実装する事が出来ました。
下記のようにscriptタグで必要なファイルを読み込んで使います。
<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <script src="opol_chiffon.js"></script><!--jsのパーサ。素晴らしい--> <script src="opol_astring.js"></script> <script src="opol.js"></script><!--opol本体--> <script src="math.js"></script> <script src="opol_math.js"></script><!--math.jsクラスを演算子登録するサンプル--> </head>
演算子の登録は下記のように行います。2項演算子であれば、左辺、右辺の2つを引数とした形で呼び出されます。
opol.operator(CPX, "+", function(L,R){return L.add(R);}); opol.operator(CPX, "-", function(L,R){return L.sub(R);}); opol.operator(CPX, "*", function(L,R){return L.mul(R);}); opol.operator(CPX, "/", function(L,R){return L.div(R);});
これで CPX.prototype["演算子"] に指定の関数が設定されました。
opol()でトランスパイルコードを作成し、eval()で実行します。
let a, b, result; eval( opol( ()=>{ let j = math.i;//虚数単位 a = 1 + 2 * j; b = 3 + 4 * j; result = (a * b) / (a + b); }))();//←()をつけて即時実行 console.log( "result=" , result ); //result= CPX {re: 0.7692307692307693, im: 1.3461538461538463} console.log( "check=" , a.mul(b).div( a.add(b) ) ); //check= CPX {re: 0.7692307692307693, im: 1.3461538461538463}
上のように、関数としてコードを書くのがオススメ。そうする事でブラウザーが変換前の段階で構文チェックしてくれる。
文字列で書くなら `~` テンプレートリテラルが便利です。
let a, b, result; eval( opol( ` let j = math.i;//虚数単位 a = 1 + 2 * j; b = 3 + 4 * j; result = (a * b) / (a + b); ` )); console.log( "result=" , result ); //result= CPX {re: 0.7692307692307693, im: 1.3461538461538463} console.log( "check=" , a.mul(b).div( a.add(b) ) ); //check= CPX {re: 0.7692307692307693, im: 1.3461538461538463}
最初にトランスパイル&関数化しておけば何度でも使えます
window.myFunc = eval( opol( function(){ return (a * b) / (a + b); })); : result = window.myFunc();
関数のトランスパイルコードをeval()で実体化した場合のスコープについて。 実体化した関数は、eval()した箇所のクロージャーになります。参考まで...。
//関数記述をeval()で実体化した場合の参考。 { let x = 1; //トランスパイルコードの生成 let opol_src = opol( function(){return x;}); { let x = 2; //変換ソースをeval()で実体化 window.myFunc = eval( opol_src ); } //ここで実行。 //eval()で実体化した場所のクロージャーになります。 result = window.myFunc(); console.log( "result=" + result ); //result=2 }
opolは演算子オーバーロードライブラリです。ライブラリというか、トランスパイラです。
JavaScriptのパーサーとジェネレータは既存のライブラリを使用しています。
opol本体。200ステップも無いです。
元ソース
chiffon.js
JavaScriptの構文解析パーサ。
JavaScriptソースプログラムをAST木に変換する素敵なプログラム。
opolでパーサとして使用しています。
opolでは、"**"等の演算子を追加して使わせていただいています。
元ソース
astring.js
JavaScriptのジェネレータ。AST木をJavaScriptのソースコードに変換するプログラム。
"export"をコメントアウトしてscriptタグで読み込めるようにして使っています。
演算子登録のサンプルです。 math.jsの便利関数をまるっと登録しています。
mathjs.org
いわずと知れた数学ライブラリ。
演算子登録のサンプルで使用しています。
Complex,BigNumber他数学系のクラスが含まれています。
算術演算子 |
+ - * / % ** |
演算子のオーバーロードの設定はopol.operator(ClassX,"+",function(left,right){ return left.add(right); } );という形になります。 ただし、opolでは左項値、右項値どちらか一方に演算子登録関数が設定されていれば、 そちらが呼び出されます。 //jに+演算子が登録されていれば呼び出しが行われる result = 1 + j;右項に"+"の演算子登録があれば、呼び出しが発生します。 その場合、左項は異なるクラスやプリミティブ型でしょう。 上のようなコードに対応するには、 演算子関数側での配慮が必要になります。たとえば opol.operator(ClassX,"+",function(left,right){ if ( !left.add ) { left = new ClassX(left); } return left.add(right); } ); |
単項符号反転 | - |
減算"-"と区別するために "-@" として設定します。opol.operator(ClassX,"-@",function(a){ return a.neg(); }); |
インクリメント デクリメント |
++ -- |
前置・後置で区別して実行されますが、オーバーロードのために設定する関数は同じです。
変数を更新する値を返却するようにします。
opol.operator(ClassX,"++" ,function(a){ return a.add(1); }); |
比較演算子 |
== != > < >= <= === !== |
設定は算術演算子と同様に行います。 |
論理演算子 |
&& || ! |
&& || のオーバーロードでは、右辺が関数として渡って来ますので注意して下さい。
opol.operator(ClassX,"&&",function(left,right_func){ return left && right_func(); });短絡評価(右項を評価しない動作)が行えるように、右辺は元の式のクロージャとして 渡るようにしているためです。上の例ではright_func()を実行すると右項の式が評価され、 結果値が得られます。 |
ビット演算子 |
! & | ^ ~ << >> >>> |
設定は算術演算子と同様に行います。単項演算子は引数1個です。 |
条件(三項)演算子 |
?: |
条件演算の設定は以下のようになります。
opol.operator(ClassX,"?",function(c,f1,f2){ return (c)? f1() : f2(); });条件式の値にオーバーロード関数が指定されていた場合に呼び出しが行われます。 結果式1,2部分f1,f2は、どちらも値そのものではなく、元の式のクロージャが渡ります。 実行する事で初めて式が評価されて、結果値が得られます。 |
代入演算子 |
= |
代入演算子の設定は以下のようになります。
opol.operator(ClassX,"=",function(c,left,right){ return right; });戻り値が左項の変数に代入されます。 ※宣言文の"="はオーバーロード出来ません。 |
複合代入演算子 |
+= -= *= /= %= **= &= |= ^= <<= >>= >>>= |
複合代入演算子の設定は以下のようになります。
opol.operator(ClassX,"+=",function(c,left,right){ return left.add(right); });つまり、"+=" の場合に設定すべき関数は "+" と全く同じです。 複合代入演算子を個別に設定していく事も出来ますが、opol.compound()が便利です。 //複合代入演算子の自動設定 opol.compound(ClassX);複合元の演算子("+"や"-"等)が設定済みであれば、 複合代入演算子にまとめて反映できます。 |
以下の演算子はオーバーロードできません。
new, typeof, delete, void, instanceof, in, yield, (), ドット, カンマ |
演算子をひとつ追加しています。演算順位は加減算の次。シフト演算と同列の順位です。
||~ |
( left * right ) / ( left + right ) 調和平均の1/2の値を返します。 LCR回路(複素数)を並列に接続する場合を想定した演算子。 1 / ( 1 / left + 1 / right ) とも書けます。 そのスジでは"//"と書くらしいのですが、JavaScriptでは"//"はコメントを意味するので使えないですね..。 ~は交流っぽい感じがするのでいいのでは...。 |
||~演算子、ほとんどの人には不要ですねwww
演算子の追加はパーサーの修正が必要で、opol_chiffon.jsを修正しています。
コードを修正して新しい演算子を作りたいって時にはチルダで終わる形が都合がよいです。
"~"は既存の演算子の後ろにくっつけると、単項演算子(ビット否定)として解釈されて
変換前でもエラーになりません。
opol_math.jsではmath.jsのComplexクラスを下記のように登録しています。
let boolCpx = function(a){return !(a.isZero() || a.isNaN());}; opol.operator(math.type.Complex,"-@" ,function(L ){return L.neg(L);}); opol.operator(math.type.Complex,"+" ,function(L,R ){if( !L.add ) return R.add(L);return L.add(R);}); opol.operator(math.type.Complex,"-" ,function(L,R ){if( !L.sub ) L = math.complex(L);return L.sub(R);}); opol.operator(math.type.Complex,"*" ,function(L,R ){if( !L.mul ) return R.mul(L);return L.mul(R);}); opol.operator(math.type.Complex,"/" ,function(L,R ){if( !L.div ) L = math.complex(L);return L.div(R);}); opol.operator(math.type.Complex,"%" ,function(L,R ){return math.re(L) % math.re(R);}); opol.operator(math.type.Complex,"**" ,function(L,R ){if( !L.pow ) L = math.complex(L); return L.pow(R);}); opol.operator(math.type.Complex,"++" ,function(L ){return L.add(1);}); opol.operator(math.type.Complex,"--" ,function(L ){return L.sub(1);}); opol.operator(math.type.Complex,"==" ,function(L,R ){return (L.equals)? L.equals(R): R.equals(L);}); opol.operator(math.type.Complex,"!=" ,function(L,R ){return !((L.equals)? L.equals(R): R.equals(L));}); opol.operator(math.type.Complex,">" ,function(L,R ){return math.re(L) > math.re(R);}); opol.operator(math.type.Complex,"<" ,function(L,R ){return math.re(L) < math.re(R);}); opol.operator(math.type.Complex,">=" ,function(L,R ){return math.re(L) >= math.re(R);}); opol.operator(math.type.Complex,"<=" ,function(L,R ){return math.re(L) <= math.re(R);}); opol.operator(math.type.Complex,"===" ,function(L,R ){return (L.equals)? L.equals(R) : R.equals(L) ;}); opol.operator(math.type.Complex,"!==" ,function(L,R ){return !((L.equals)? L.equals(R) : R.equals(L));}); opol.operator(math.type.Complex,"&&" ,function(L,fR ){return boolCpx(L)? fR() : L;}); opol.operator(math.type.Complex,"||" ,function(L,fR ){return boolCpx(L)? L : fR();}); opol.operator(math.type.Complex,"!" ,function(L ){return !boolCpx(L);}); opol.operator(math.type.Complex,"&" ,function(L,R ){return math.re(L) & math.re(R);}); opol.operator(math.type.Complex,"|" ,function(L,R ){return math.re(L) | math.re(R);}); opol.operator(math.type.Complex,"^" ,function(L,R ){return math.re(L) ^ math.re(R);}); opol.operator(math.type.Complex,"~" ,function(L ){return ~math.re(L) ;}); opol.operator(math.type.Complex,"<<" ,function(L,R ){return math.re(L) << math.re(R);}); opol.operator(math.type.Complex,">>" ,function(L,R ){return math.re(L) >> math.re(R);}); opol.operator(math.type.Complex,">>>" ,function(L,R ){return math.re(L) >>> math.re(R);}); opol.operator(math.type.Complex,"?" ,function(C,fL,fR){return boolCpx(C)? fL() : fR();}); opol.operator(math.type.Complex,"||~" ,function(L,R ){ if( !(L instanceof math.type.Complex)) L=math.complex(L); return L.mul(R).div(L.add(R));}); //||~ (z1*z2)/(z1+z2) opol.compound(math.type.Complex);//複合代入系演算子を一括設定
遅いです。
演算子が関数2回の呼び出しに代わるだけなのですが、
Chromeで計測すると、素の演算子に比べると1000倍くらい遅い。
JITコンパイラの最適化がうまくいかないんでしょうか...。
項が組み込みの型だと分かる場合は演算子変換しないなど、工夫の余地はありそうです。
ダウンロード
MITライセンスでここに置いておきます。使いたい人がいるか分かりませんがご自由にどうぞ。
opol.zip
サンプル1
opol_sample.html
上のテキストエリアに入力したコードをexecuteボタンで実行します。
サンプル2
ooCons.html
ブラウザの開発者モードっぽく式を実行できます。
↓opol.js全コード
//opol.js relese 1.0 Copyright (c) 2018, Bachagi.h//Licensed under the MIT license. { const global = Function("return this")();//window const src2ast = function(src) { return Chiffon.parse(src); }; //使用するパーサーを指定 const ast2src = function(node) { return astring.generate(node); }; //使用するジェネレータを指定 const clone = function(node){ return JSON.parse(JSON.stringify(node)); }; //const debugPrint = console.log; const debugPrint = function(){}; ////////////////////////////////////////////////////////////////////////// //プログラムのトランスパイル global.opol = function(src){ return opol.transpile(src.toString() ); }; //////////////////////////////////////////////////////////////////////////// //演算子ごとの定義 const optbl = { // : { func:通常演算処理 ,ptn:オーバーロード雛形} "-@" : { func:(L,R) => -L ,ptn:'opol.opu("-@" ,L ) '}, "+" : { func:(L,R) => L + R ,ptn:'opol.opb("+" ,L,R) '}, "-" : { func:(L,R) => L - R ,ptn:'opol.opb("-" ,L,R) '}, "*" : { func:(L,R) => L * R ,ptn:'opol.opb("*" ,L,R) '}, "/" : { func:(L,R) => L / R ,ptn:'opol.opb("/" ,L,R) '}, "%" : { func:(L,R) => L % R ,ptn:'opol.opb("%" ,L,R) '}, "**" : { func:(L,R) => L ** R ,ptn:'opol.opb("**" ,L,R) '}, "==" : { func:(L,R) => L == R ,ptn:'opol.opb("==" ,L,R) '}, "!=" : { func:(L,R) => L != R ,ptn:'opol.opb("!=" ,L,R) '}, ">" : { func:(L,R) => L > R ,ptn:'opol.opb(">" ,L,R) '}, "<" : { func:(L,R) => L < R ,ptn:'opol.opb("<" ,L,R) '}, ">=" : { func:(L,R) => L >= R ,ptn:'opol.opb(">=" ,L,R) '}, "<=" : { func:(L,R) => L <= R ,ptn:'opol.opb("<=" ,L,R) '}, "===" : { func:(L,R) => L === R ,ptn:'opol.opb("===" ,L,R) '}, "!==" : { func:(L,R) => L !== R ,ptn:'opol.opb("!==" ,L,R) '}, "&&" : { func:(L,fR) => L && fR() ,ptn:'opol.opl("&&" ,L,()=>R) '}, "||" : { func:(L,fR) => L || fR() ,ptn:'opol.opl("||" ,L,()=>R) '}, "!" : { func:(L) => !L ,ptn:'opol.opu("!" ,L ) '}, "&" : { func:(L,R) => L & R ,ptn:'opol.opb("&" ,L,R) '}, "|" : { func:(L,R) => L | R ,ptn:'opol.opb("|" ,L,R) '}, "^" : { func:(L,R) => L ^ R ,ptn:'opol.opb("^" ,L,R) '}, "~" : { func:(L) => ~L ,ptn:'opol.opu("~" ,L ) '}, "<<" : { func:(L,R) => L << R ,ptn:'opol.opb("<<" ,L,R) '}, ">>" : { func:(L,R) => L >> R ,ptn:'opol.opb(">>" ,L,R) '}, ">>>" : { func:(L,R) => L >>> R ,ptn:'opol.opb(">>>" ,L,R) '}, "=" : { func:(L,R) => R ,ptn:'opol.asb("=" ,(v)=>L=v,L,R) '}, "+=" : { func:(L,R) => L + R ,ptn:'opol.asb("+=" ,(v)=>L=v,L,R) '}, "-=" : { func:(L,R) => L - R ,ptn:'opol.asb("-=" ,(v)=>L=v,L,R) '}, "*=" : { func:(L,R) => L * R ,ptn:'opol.asb("*=" ,(v)=>L=v,L,R) '}, "/=" : { func:(L,R) => L / R ,ptn:'opol.asb("/=" ,(v)=>L=v,L,R) '}, "%=" : { func:(L,R) => L % R ,ptn:'opol.asb("%=" ,(v)=>L=v,L,R) '}, "**=" : { func:(L,R) => L ** R ,ptn:'opol.asb("**=" ,(v)=>L=v,L,R) '}, "&=" : { func:(L,R) => L & R ,ptn:'opol.asb("&=" ,(v)=>L=v,L,R) '}, "|=" : { func:(L,R) => L | R ,ptn:'opol.asb("|=" ,(v)=>L=v,L,R) '}, "^=" : { func:(L,R) => L ^ R ,ptn:'opol.asb("^=" ,(v)=>L=v,L,R) '}, "<<=" : { func:(L,R) => L << R ,ptn:'opol.asb("<<=" ,(v)=>L=v,L,R) '}, ">>=" : { func:(L,R) => L >> R ,ptn:'opol.asb(">>=" ,(v)=>L=v,L,R) '}, ">>>=" : { func:(L,R) => L >>> R ,ptn:'opol.asb(">>>=",(v)=>L=v,L,R) '}, "++" : { func:(L) => L+1 ,ptn:'opol.asu("++" ,(v)=>L=v,L) '}, "--" : { func:(L) => L-1 ,ptn:'opol.asu("--" ,(v)=>L=v,L) '}, "@++" : { func:(L) => L+1 ,ptn:'opol.asp("@++" ,(v)=>L=v,L) '}, "@--" : { func:(L) => L-1 ,ptn:'opol.asp("@--" ,(v)=>L=v,L) '}, "||~" : { func:(L,R) => (L===0&&R===0)?0 : (L*R)/(L+R) ,ptn:'opol.opb("||~" ,L,R) '}, "?" : { func:(C,fL,fR) => (C)? fL() : fR() ,ptn:'opol.opc("?" ,C,()=>L,()=>R) '}, //typeof delete void instanceof等の演算子は非対応 }; const D={}; //演算子処理テーブルの前処理 for( let ops in optbl ) { optbl[ops].ast = src2ast(optbl[ops].ptn).body[0].expression;//オーバーロード雛形をASTに変換しておく。 D[ops] = optbl[ops].func;//通常の演算子処理 } //////////////////////////////////////////////////////////////////////////// //トランスパイル opol.transpile = function(src){ const node = src2ast(src); //元ソースを構文解析しast木を取得 debugPrint("src2ast=" , node); nodeConv(node,"",null); //演算子を関数呼び出しに置換 let dst = ast2src(node); //ソースを生成 if( node.body[0].type === "FunctionDeclaration" ) dst = "("+ dst + ")";//関数は式に dst = dst.replace( /;\n$/,"");//式の場合、末尾の;を削除。 debugPrint("変換結果:" + dst ); return dst; }; //nodeを指定して演算子部分を関数に置換 const nodeConv = function(node,name,parent_node){ if( node && typeof node === "object" ) { for( let key in node) { nodeConv( node[key], key, node); } switch(node.type){ case 'BinaryExpression'://2項演算子 case 'LogicalExpression'://&&論理演算 case 'AssignmentExpression'://代入演算子 opConv(node,name,parent_node, node.left,node.right); break; case 'UnaryExpression'://単項演算子 case 'UpdateExpression'://++,--演算子 opConv(node,name,parent_node, node.argument); break; case 'ConditionalExpression'://条件演算子 opConv(node,name,parent_node, node.consequent,node.alternate,node.test); break; } } }; //演算子1箇所指定してを関数呼び出しに置換する const opConv = function(node, name, parent_node, nodeL, nodeR, nodeC){ let ops = node.operator; if( node.type === 'UnaryExpression' && ops === '-' ) ops = "-@";//2項マイナスと別名に if( node.prefix===false && (ops==="++" || ops==="--") ) ops = "@" + ops;//a++,a--//前置と別名に if( node.type === 'ConditionalExpression' ) ops = '?';//条件演算子 if( ops in optbl ) { let ast = clone( optbl[ops].ast );//置換雛形取得 convLR(ast,"expression",null,nodeL, nodeR, nodeC);//雛形のL,Rを本物に置換 debugPrint( "convLR結果=", ast ) parent_node[name] = ast;//本体を関数呼び出し形式に置換 }//無い場合は置換しない。 }; //演算子関数呼出し雛形ASTを、実際の左項、右項目のnodeに差し替える const convLR = function(ast, name, parent_ast, nodeL, nodeR, nodeC){ if( ast && typeof ast === "object" ) { for( let key in ast) { convLR( ast[key],key, ast, nodeL, nodeR, nodeC); } if( ast.type === "Identifier" ){ if( nodeL && ast.name === "L" ) parent_ast[name] = nodeL; if( nodeR && ast.name === "R" ) parent_ast[name] = nodeR; if( nodeC && ast.name === "C" ) parent_ast[name] = nodeC; } } }; //////////////////////////////////////////////////////////////////////// //クラスに演算子処理関数を登録する。 opol.operator = function(__class,ops,func) { const className = __class.name || "className"; let err = false; if( typeof __class !== "object" && typeof __class !== "function" ) { console.warn( "opol.operator([" + className + "],'" + ops + "', function) is not class." ); err = true;//引数__classの型が不正。 } if( typeof optbl[ops] !== "object" ) { console.warn( "opol.operator(" + className + ",['" + ops + "'], function) unsupported operator." ); err = true;//引数opsが不正。不明な演算子。 } if( typeof func !== "function" ) { console.warn( "opol.operator(" + className + ",'" + ops + "', [function]) is not functiond." ); err = true;//引数funcが関数ではない } __class.prototype[ops] = func;//登録 if( ops === "++" || ops === "--" ) { __class.prototype["@" + ops] = func;//++,--の場合後置の場合の設定も行う。 } return err; }; //複合代入演算子を一括適用。*= に * の関数を適用する。 opol.compound = function(__class){ if( "+" in __class.prototype ) opol.operator(__class,"+=" ,__class.prototype["+"] ); if( "-" in __class.prototype ) opol.operator(__class,"-=" ,__class.prototype["-"] ); if( "*" in __class.prototype ) opol.operator(__class,"*=" ,__class.prototype["*"] ); if( "/" in __class.prototype ) opol.operator(__class,"/=" ,__class.prototype["/"] ); if( "%" in __class.prototype ) opol.operator(__class,"%=" ,__class.prototype["%"] ); if( "**" in __class.prototype ) opol.operator(__class,"**=" ,__class.prototype["**"] ); if( "&" in __class.prototype ) opol.operator(__class,"&=" ,__class.prototype["&"] ); if( "|" in __class.prototype ) opol.operator(__class,"|=" ,__class.prototype["|"] ); if( "^" in __class.prototype ) opol.operator(__class,"^=" ,__class.prototype["^"] ); if( "<<" in __class.prototype ) opol.operator(__class,"<<=" ,__class.prototype["<<"] ); if( ">>" in __class.prototype ) opol.operator(__class,">>=" ,__class.prototype[">>"] ); if( ">>>" in __class.prototype ) opol.operator(__class,">>>=",__class.prototype[">>>"] ); }; //////////////////////////////////////////////////////////////////////// //実行時演算子処理実体 opol.opb = (ops,L,R) => ((L&&L[ops]) || (R&&R[ops]) || D[ops])(L,R); //2項演算 opol.opu = (ops,L) => ( (L&&L[ops]) || D[ops])(L); //単項演算 opol.asb = (ops,setter,L,R) => setter( ((L&&L[ops]) || (R&&R[ops]) || D[ops])(L,R) ); //2項代入演算 opol.asu = (ops,setter,L) => setter((L[ops] || D[ops])(L)); //前置単項更新(++a.--a) opol.asp = (ops,setter,L) => (setter((L[ops] || D[ops])(L) ), L); //後置単項更新(a++.a--) opol.opl = (ops,L,fR) => ((L&&L[ops]) || D[ops])(L,fR); //2項論理演算。右辺をクロージャ渡し。 opol.opc = (ops,C,fL,fR) => ((C&&C[ops]) || D[ops])(C,fL,fR); //3項条件演算 }