ルールエンジンのルールをオブジェクトとして表す方法は?
ルールは
if(booleanExpression(input))then一連の一般的なアクション "その他次のルール
...一般的なアクションは、たとえば、別のルールチェーンに入力を渡す、結論を返す(結果で分析を終了する)、または追加のデータを収集する-ただし、おそらく他のアクションも必要になるかもしれません。
ここで、任意のブール条件を解析する問題(脇にあるbooleanExpression
クラスが完全に機能していると仮定します)、そのようなRuleオブジェクトはどのように見えるか、具体的には、規則的に相互接続されたグリッドを含むRulesetオブジェクトはこれらは、特に実際のエンジンで処理できる構造に似ています。 (もしデータ構造がそのようなエンジンがそれらをどのように処理すべきかを明確にしないなら、おそらくそれについてのヒントも?)
これはC++で書かれますが、言語に依存しない答えも大歓迎です。
これは Event Correlator に関する私の質問の派生物です。これは、1つの回答で対処するには広すぎるために答えられないように思われるため、問題をより小さな一口サイズのチャンクに分割しようとしています。
ルールがすでに定義されている場合、対応するデータ構造は単純です(疑似コード)。
_class Rule {
Condition condition;
ActionSet positiveActions, negativeActions
}
_
ただし、rulesetへの参照で示したように、ルールエンジンの興味深い部分は、ルール選択のプロセスとその実行戦略です。恐らくあなたが信じているのと同じくらい多くの変種があり、それに加えていくつかあります。
ただし、通常、ルールエンジンのルール-少なくとも私が見た約12のルールエンジン-には、「else」部分が含まれていません。ルールは、適用するときにいくつかの条件があるという考え方ですが、適用しない場合は適用されません。したがって、エンジン開発は2つのコアタスクに分類されます。
適用可能なルールを決定
これに関する多数の研究論文が利用可能です。適用可能なルールの選択は、ルールを表すために使用されるデータ構造に大きく依存します。本当に任意のブール条件を許可したい場合は、条件がすべての可能な入力で評価されない限り、何も決定できません。したがって、ほとんどのルールエンジンには、いくつかのオブジェクト/述語/制約/呼び出されるものは何でもあり、それを適用できるルールの部分に使用する必要があります。これにより、エンジンはすべてのルールからインテリジェントに(読み取り:効率的に)選択し、適用可能なルールを入力できます。
このようなものがf.exでどのように見えるかについてのより具体的な例を次に示します。プロローグ:
MySignal(_, true, false) :- do_something
MySignal(_, true, true) :- do_something_else
明らかに、実装はこれらのルールがMySignal
にのみ適用され、2番目の引数がtrue
の場合にのみ適用されるという事実を悪用する可能性があります。したがって、MySignal
専用のルックアップテーブルを使用してルールのルックアップを改善することができます。
ルールに加えて、1つの非常に重要な他の側面、つまり入力、またはより一般的には、これらのルールが機能することになっているデータがあることに注意してください。ほとんどのルールエンジンでは、ルールを適用することでこのデータを変更することがさらに可能です(つまり、何かから始めて、ルールがそのデータに新しいものを追加します)。この例では、MySignal
入力があるかどうかを効率的に判別できるように、入力を格納する必要があります(ここでも、何らかのルックアップテーブルを想定できます)。
この入力処理には、ルールへの接続も必要です。ルールエンジンでは、これは上記のオブジェクト/述語などを介して、または論理変数を介して実行されます。言い換えると、ルールが実行する必要があるアクションは通常、入力に依存するため、入力は何らかの方法でアクションに渡すことができる必要があります。より具体的には、次の例を検討してください。
MySignal(X, false, false) :- print(X)
適用可能なルールの実行戦略を決定する
ほとんどのエンジンは、ここでは単純な上から下への戦略にフォールバックします。これは、入力に一致するルールファイル内のテキストの最初のルールが適用されるためです。ここでも任意に複雑にできます。考えられるさまざまな戦略のほんの数例:
したがって、本質的に、あなたの質問はどちらの方法に行きたいかということになります:本格的なルールエンジン、または単純なフィルターマップの組み合わせ。
完全を期すために、あなたのケースに適用できるかもしれない偽のルールエンジンアプローチを次に示します(else-partのない機能的な疑似コードですが、それは明確でなければなりません)。この場合、ルールの選択は、入力が条件を満たしていることを確認することであり、実行戦略は、適用可能なすべてのルールをテキスト順に適用することです。
_let I be the list of inputs
let R be a list of rules
I map (i => R filter ( r => r.condition(i) == true ) map ( r => r.action(i) )
_
明らかに、条件と入力の各組み合わせをチェックする必要があるため、ルールの数が増えるとランタイムの複雑さが非常に悪くなります。結局のところ、ルールエンジンの主な特徴は、この組み合わせの爆発に対処する手段を提供することです。
適切なルールエンジンに興味がある場合は、これらを実装する方法についてのクエストを開始するためのいくつかの追加キーワードを次に示します。ルールベースの運用セマンティクス、RETE、統合
さて、これをどうやって呼び出すのですか?
私はそれがこのようなものだと思います:
for (auto rule: ruleset) {
if (rule.matches(context)) {
if (rule.execute() == Stop)
break # so rules can stop early
# else: default action is to continue
}
}
だから、あなたは2つのメソッドを持つオブジェクトを持っています
bool Rule::matches(Context const& input) { return booleanExpression(input); }
void execute() {
for (auto action: actions) action.execute();
}
それはあなたが探しているものですか?これは単純な線形ルールセット(分岐が必要ですが、iptablesと少し似ています)でも問題なく機能しますが、決定木では、使用可能なアクションの1つが「このネストされたサブツリーを再帰的に評価する」必要があります。ルールセットがある場合は、小さなサンプルルールセットを使用して簡単に説明できます。
実際のブール式を使用して離れて、開発している「システム」に対して実行しているアクションまたはイベントを把握することをお勧めします。 ifステートメントを使用している場合は、スクリプト言語を使用することもできます。
むかしむかしブール式を使用する代わりに、アクションまたはイベントとしてルールを定義したDSLをJSON形式で作成しました。 JSONを選択したのは、DSLコードがWebバックエンドから生成され、JavaScript Webフロントエンドで使用されるためです。
1人のユーザーがデザイナーUIで取ることができる一連のオプションを検討してください。これらはすべて名前で個別に識別できます。以下の例のデザイナーUIは、ユーザーが選択できる自動車になります。 DSLは基本的に次のようになりました。
// an array of rules
"rules": [
// rule number 1
{
// the originating option of the event or action
"source": "car_1",
// the option(s) that receives of the action or event
"destination": ["color_yellow", "color_blue"],
// the event or action that should happen
// in this case the destination should be disabled
"action": "disable",
// what to roll back to if the destination was selected
"instead": "color_red"
},
// rule number 2, same as above but used to
// demonstrate chaining
{
"source": "roof_sunroof",
"destination": "color_red",
"action": "disable",
"instead": "color_green"
},
... and so on
]
この例では、最初のルールでは、ユーザーがオプション「car_1」を選択した場合、カラーオプション「color_yellow」と「color_blue」が無効になります(その特定の車には黄色と青色がないため)。 2番目のルールでは、ユーザーが「roof_sunroof」を選択すると、「color_red」のオプションが無効になります。
一方、フロントエンドはこのJSONファイルを受け取り、ユーザーインターフェイスのすべてのオプションを作成し、ルール配列を使用して、設定するイベントを決定します。
アクションとイベントは、ルールセットによってチェーン可能になるように拡張されて実装されたため、1つのソースが無効になり、ロールバックオプションも無効にされた場合、イベントはロールバック先を検索します。上記の例では、オプション「roof_sunroof」を選択してから「car_1」を選択した場合、アプリケーションはまず「color_red」(無効)にロールバックしてから、「color_green」にロールバックしようとします。循環依存があった場合、それはおそらくデザイナーUIではなくDSLのバグです。イベントは、複雑なツリーを構築する必要なくルールを適用することに注意してください。
バックエンド側では、DSLの定義であれば、ルールをオブジェクトとして表現する方が簡単です。確かにifステートメントのルートに行けば、ルールをテキストスクリプトとして永続化できます。
これはあなたの質問に対してやや斜めになりますが、この場合あなたが話しているのは実際にはドメイン固有言語の作成であるかのように聞こえます-それらを研究し始めると、あらゆる種類の興味深い記事が見つかります。 Martin Fowlerは良い出発点を提供します。
DSLは意識するのに非常に有用な分野であり、今後数年間でもっと多く見られるようになると思います。