#概要
普段のプログラムを逆ポーランド記法(後置記法)で書きたいとは思いませんが…
プログラミングに明るくない方に簡単なスクリプティングのようなことをしてもらう必要がある場合、日本語の識別子、日本語の語順(≒逆ポーランド記法)のDSLを用意できると便利なこともあるかもしれません。
というわけで何個目の車輪か見当も付きませんが作ってみました。
See the Pen rpn by shinji (@shinji709) on CodePen.
#逆ポーランド記法(後置記法)について少し
2つの引数をとり加算結果を返す加算演算子+
を考えたとき、呼び出しの際+ 1 2
などと関数を引数より前に置く記法を前置記法、1 + 2
などと引数と引数の間に置く記法を中置記法、1 2 +
などと引数の後ろに置く記法を後置記法といいます。
後置記法では1と2を足す
と自然と読み下すことができ、日本語と相性がいいです。中置記法と違い計算順序が一意に定まりカッコが要らないなど他の利点もあるようですが、本トピックで逆ポーランド記法を採用するのはこの理由だけです。
#コード主要部の説明
主要な部分は以下7行だけです。(続けて四則演算の単純な使用例も付記しています)
const createRpn = dictionary => words => words.reduce((acc, word) => {
if (word in dictionary) {
return acc.concat(dictionary[word](...acc.splice(-dictionary[word].length || Infinity)));
} else {
return [...acc, word];
}
}, []);
// 以下使用例
// 辞書の定義
const dictionary = {
足す: (a, b) => Number(a) + Number(b),
引く: (a, b) => Number(a) - Number(b),
掛ける: (a, b) => Number(a) * Number(b),
割る: (a, b) => Number(a) / Number(b),
};
// DSL処理関数の生成
const rpn = createRpn(dictionary);
// サンプルDSLコード
const code = `
3 4 足す
8 5 引く
割る
`;
const codeArr = code.trim().split(/\s+/);
console.log(codeArr);
//=> ["3", "4", "足す", "8", "5", "引く", "割る"]
// 中置記法で書くと(3 + 4) / (8 - 5)ということ
// 処理
const result = rpn(codeArr);
console.log(result);
//=> [2.3333333333333335]
DSL内の識別子をキー、その関数実装を値として持つオブジェクト、dictionaryを取り、コードを配列として取って結果を返す関数を返します。
処理時は、受け取ったコード(文字列の配列)をreduceにより先頭から順に処理していき処理結果としての新しい配列を作ります。
文字列が辞書に定義されていなければ、文字列をそのまま結果配列に入れていきます。
文字列が辞書に定義されている単語であれば、その実装関数の仮引数の個数(Function.prototype.lengthにより取得)分の要素を結果配列からpopし(popは使ってませんが…)、それを引数として実装関数を呼び出し、戻り値を結果配列に入れます。
実装にあたってはプログラミング言語Forthをほんの少し参考にしています。
「ほんの少し」というのは、「ほとんどオレが考えたんだぜ」というのではもちろんなく、せっかく参考にさせていただいたが本家の足元にも及ばないという意味です。念の為。(ちなみにwordとか、dictionaryというのはforthの用語です)
#UIコード部の説明
主要コード部以外はほぼUIコードです。
コード入力エリアのonchangeで実行しようと思ってましたが、サンプルコードにユーザー入力を入れた関係でいちいち実行ボタンを押す形式に変更しました。
コード入力エリアの右にはdictionaryオブジェクトに定義されている単語を列挙し、ボタンを押すことでカーソル位置に単語を挿入できるようにしています。もちろん直接入力でもいいです。またPCであればボタンホバーで各単語の実装がツールチップで表示されます。(横着してtitle属性を使いました)
#まとめ・感想
思っていたより簡単にできました。JavaScriptのコードで言う、foo();bar();baz()
とか、foo(bar(baz()))
などの単純な呼び出しのみの処理だからですね。
制御構造を入れだすととたんに難しくなりそうな気がしますが、追々挑戦していきたいです。
JavaScriptはライブラリが充実していてけっこう何でもあるので今回のコード利用して色々日本語DSL作ってみたいですね。まあ、自分では使わないんですけどね。
(今回のサンプルでは、"D"SLっていうけど一体どんなドメインなんだ?というようなしょうもないdictionaryですみません。思いつかなかったんです…)
以上です。
質問、追記・修正要請あればコメント欄にお願いします。