| Oracle® Fusion Middleware Oracle Business Process Managementルール言語リファレンス 12c (12.2.1) E72514-01 |
|
前 |
次 |
この章では、Oracle Business Rules RL Language (RL Language)の概念について説明します。
この章の内容は次のとおりです。
Oracle Business Rules環境は、rl.jarで提供されるクラスによってJVMコンテナまたはJ2EEコンテナに実装されます。
コマンドライン・インタフェース使用のRL Languageは、次のコマンドを使用して開始します。
java -jar $SOA_ORACLE_HOME/soa/modules/oracle.rules_11.1.1/rl.jar -p "RL> "
ここで、ORACLE_HOMEは、SOAモジュールがインストールされる場所です(例: c:/Oracle/Middleware)。-pオプションはプロンプトを指定します。
RL Languageコマンドライン・インタフェースにより、Oracle Business Rules RuleSessionにアクセスできます。このRuleSessionは、JavaプログラマがJavaアプリケーションでRL LanguageにアクセスするためのAPIです(コマンドライン・インタフェースでは、RuleSessionを内部的に使用します)。
コマンドライン・オプションの詳細は、「コマンド・ライン・インタフェースの使用」を参照してください。Oracle Business Rules RuleSession APIの詳細については、「RuleSessionの使用」を参照してください。
例1-1に示すように、RL>プロンプトにテキストを入力し、コマンドライン・インタフェースを使用してプログラムを実行できます。
例1-1 コマンドライン・インタフェースの使用
RL> println(1 + 2); 3 RL> final int low = -10; RL> final int high = 10; RL> println(low + high * high); 90 RL> exit;
RL Languageのルールセットでは、Javaパッケージと同様に、RLクラス、関数およびルール用の名前空間が提供されます。さらに、ルールセットを使用すると、ルールの起動順序を部分的に指定できます。ルールセットには、実行可能なアクションおよびその他のルールセットを含めたり、Javaのクラスやパッケージをインポートすることもできます。
RL Languageのルールは、ルール条件(fact-set-conditionとも呼ばれます)とaction-block(アクションのリスト)で構成されています。ルールは、ルール条件に続いてルール・アクションがあるif-then構造に従っています。
例1-2に、「Hello World」と出力するプログラムを示します。この例は、デフォルト・ルールセット(main)に1つの最上位のアクションがあるプログラムを示しています。例1-2には1つのアクションのみが含まれ、ルールは定義されていないため、アクションはコマンドラインで即時に実行されます。
例1-2 「Hello World」プログラミング例
RL> println("Hello World");Hello World
RL>
ルール条件はルールの1コンポーネントで、ファクトを参照する複数の条件式で構成されています。
次の例では、条件式がファクト(Driverインスタンスのd1)を参照し、次に、そのファクトのデータ・メンバーageが16未満であることをテストします。
if (fact Driver d1 && d1.age < 16)
例1-3に、RL Languageで記述された完全なルールを示します(このルールには、1つのルール条件と1つのルール・アクションが含まれています)。
Oracle Rules Engineは、条件式がtrueになるようなファクトの組合せがあるたびにルールをアクティブにします。ルール条件はOracle Rules Engine内の使用可能なファクト全体に対する問合せのようなもので、問合せから戻される行のそれぞれに対してルールがアクティブになります。
注意:
ルールのアクティブ化とルールの起動は異なります。詳細は、「ルール起動の理解と制御」を参照してください。
例1-3 運転者の年齢ルールの定義
RL> rule driverAge{
if (fact Driver d1 && d1.age < 16)
{
println("Invalid Driver");
}
}
ルール・アクションは、すべてのルール条件が満たされた場合にアクティブ化されます。ルールのaction-blockで実行されるアクションには、いくつかの種類があります。たとえば、ルールのaction-block内のアクションでは、assert関数をコールして新規ファクトを追加したり、retract関数をコールしてファクトを削除できます。また、アクションでJavaメソッドやRL Language関数を実行することもできます(例1-3ではprintln関数が使用されています)。アクションを使用すると、パターン一致に関連した任意のタスクを実行する関数をコールできます。
この項では、Oracle Business Rulesファクトについて説明します。
次の項が含まれます:
Oracle Business Rulesのファクトは、アサートされたオブジェクトです。Javaオブジェクトの場合、ファクトはオブジェクトのシャロー・コピーです。これは、可能な場合は各プロパティがクローニングされ、それが可能でない場合はファクトがJavaオブジェクト参照のコピーになることを意味します。
RL Languageでは、JavaオブジェクトはJavaクラスのインスタンスであり、RLオブジェクトはRL Languageクラスのインスタンスです。Javaクラスをクラスパスで使用したり、RL Languageクラスを定義してルールセットで使用できます。また、ファクト・クラス宣言を使用して、Javaクラスの既存のプロパティまたはメソッドに関連付けられている追加プロパティを宣言することもできます。さらに、ファクト・クラス宣言を使用して、ファクトで不要なJavaクラスのプロパティを非表示にできます。
RL Languageクラスは、メソッドを除いたJava Beanに類似しています。RLクラスには、複数の名前付きプロパティが含まれます。各プロパティのタイプは、RLクラス、Javaオブジェクトまたはプリミティブ型のいずれかになります。
Oracle Business Rulesを使用する際は、通常、Javaクラス(XMLの使用をサポートしているJAXB生成クラスを含む)を使用して、ルール対応アプリケーションにビジネス・オブジェクトを調べるルールを作成したり、その結果をアプリケーションに戻します。Oracle Rules Engineで他のルールをトリガーできる中間ファクトを作成するには、通常、RLクラスを使用します。
Oracle Business Rulesでは、作業メモリーを使用してファクトを追加します(ファクトは作業メモリー以外には存在しません)。作業メモリーはRuleSessionに含まれます。
RL Languageのファクトは、クラスのアサートされたインスタンスです。例1-4に、RLクラスenterRoomのインスタンスをファクトとして作業メモリーに追加するassert関数を示します。アサートされたファクトの基礎となるクラスは、JavaまたはRL Languageで定義できます。
例1-4では、sayHelloルールがタイプenterRoomのファクトに一致すると、一致したファクトごとにメッセージが出力されます。assert関数内のアクションnewは、enterRoomクラスのインスタンスを作成します。
例1-4では、run関数によってsayHelloルールが起動します。
注意:
RL LanguageのnewキーワードはJavaのnew機能の拡張で、プロパティに対して初期値を指定できます。
例1-4 RL Languageクラスによる定義したファクトの照合
RL> class enterRoom { String who; }
RL> assert(new enterRoom(who: "Bob"));
RL> rule sayHello {
if ( fact enterRoom ) {
println("Hello " + enterRoom.who);
}
}
RL> run();
Hello Bob
RL>
RL Languageクラスをルール・プログラムで使用すると、Javaオブジェクトを提供するJavaアプリケーションのアプリケーション・コードを変更せずに、Javaアプリケーションのオブジェクト・モデルを補完できます。
詳細は、「ファクトをアサートして作業メモリーに追加」を参照してください。
例1-5に、顧客データcustを含むJavaクラスを使用するgoldCustルールを示します。このルールのアクションは、GoldCustomer RLクラスのインスタンスをアサートし、3か月間の支払が500ドルを超える顧客を示します。Java CustomerクラスにはメソッドSpentInLastMonthsが含まれています。このメソッドには、追加する顧客データの月数を示す整数を指定します。
例1-5 goldCustルール
rule goldCust {
if (fact Customer cust && cust.SpentInLastMonths(3) > 500 ){
assert (new GoldCustomer(cust: cust));
}
}
例1-6に示すgoldDiscountルールは、RLファクトGoldCustomerを使用して、過去3か月間に$500の支払があった顧客には10%割引の資格があることを示しています。
例1-6 goldDiscountルール
rule goldDiscount {
if (fact Order ord & fact GoldCustomer(cust: ord.customer) )
{
ord.discount = 0.1;
assert(ord);
}
}
例1-7に、GoldCustomer RLクラスの宣言を示します(ここでは、Customerクラスがクラスパスで使用可能であることを前提にしています)。
例1-7 RL Languageクラスの宣言
class GoldCustomer {
Customer cust;
}
アサートされたJavaオブジェクトは、ファクトとしてRL Languageプログラムで使用できます。Javaクラスを明示的に定義または宣言する必要はありません。ただし、プログラムを実行するときは、Javaクラスをクラスパスに含める必要があります。これによって、Javaクラスをルールで使用でき、ルール・プログラムは、Javaクラスに定義されているpublic属性、publicメソッドおよびBeanプロパティにアクセスして使用できます(一部のアプリケーションにはBeanプロパティが適切です。これは、JavaオブジェクトがPropertyChangeListenerをサポートしていることがOracle Rules Engineで検出される場合があるためです。この場合は、オブジェクトの変更時に通知するメカニズムが使用されます)。
さらに、ファクト・クラス宣言によって、プロパティをRLプログラムで使用できるように微調整できます。また、ファクト・クラス宣言は、継承が行われるいくつかの状況で必要になる場合があります。
Javaクラスを使用すると、import文を使用してパッケージ名を省略できます(例1-8を参照)。
例1-8 import文を使用したJavaファクトの例
ruleset main
{
import example.Person;
import java.util.*;
rule hasNickNames
{
if (fact Person p && ! p.nicknames.isEmpty() )
{
// accessing properties as fields:
println(p.firstName + " " + p.lastName + " has nicknames:");
Iterator i = p.nicknames.iterator();
while (i.hasNext())
{
println(i.next());
}
}
}
この項の内容は次のとおりです。
Oracle Rules Engineは、作業メモリーの状態に変化があると、ファクトをすべてのルールのルール条件(fact-set-condition)と照合します。作業メモリー内に変更が生じた場合(通常はファクトがアサートまたは取り消された場合)、Oracle Rules Engineでは、ルール条件に一致するかどうかのみがチェックされます。特定のルール条件がtrueとなるファクトのグループをファクト・セット行と呼びます。ファクト・セットは、特定のルールに対するすべてのファクト・セット行のコレクションです。したがって、ファクト・セットは、1つのルールに含まれる複数のルール条件に一致するファクトで構成されます。ファクト・セット内のファクト・セット行ごとに、ファクト・セット行とルールへの参照で構成されたアクティブ化がアジェンダに追加されます(アジェンダには、アクティブ化の完全リストが含まれます)。
図1-1に示すRuleSessionには、作業メモリー内に、アクティブ化が含まれるアジェンダがあります。
run、runUntilHaltおよびstepの各関数は、アジェンダにあるアクティブ化を実行します。つまり、これらのコマンドによってルールが起動します(指定した数のアクティブ化を起動するには、stepコマンドを使用してください)。
Oracle Rules Engineがアクティブ化を削除すると、アジェンダからアクティブ化を削除し、ルールのアクションを実行して、ルールが起動します。
Oracle Rules Engineでは、ルール条件が満たされなくなると、ルールを起動せずにアクティブ化を削除できます。たとえば、ファクトが変更された場合、またはルールが消去された場合は、ルールを起動せずにアクティブ化を削除できます。さらに、ファクト・セット行で参照されるファクトが変更された場合、またはファクトが取り消された場合はルール条件に一致しなくなるため、Oracle Rules Engineではアクティブ化がアジェンダから削除されます(新規ファクトがアサートされた場合、および!演算子が適用された場合も同様です)。
ルールのアクティブ化については、次の点に注意してください。
アクティブ化が作成されてルールが起動するのは、ファクトがアサート、変更または取り消された場合のみです(それ以外の場合、ルールは連続して起動します)。
ルール条件に記述されているファクトがルールによってアサートされた後も、ルール条件がtrueの場合は、新規のアクティブ化がアジェンダに追加され、ルールが再度起動します(この場合、ルールは連続して起動します)。多くの場合、この動作は不適切です。
ルール起動に関連したアクションによってファクトが変更、アサートまたは取り消されると、アジェンダにある一連のアクティブ化が変更される場合があり、これによって起動するルールの順序が変更される場合があります。
ルールは、同時ではなく順番に起動します。
関連項目:
watchActivations、watchFacts、watchRulesおよびshowFactsの各関数は、RL Languageプログラムの記述とデバッグに有用です。
この項の内容は次のとおりです。
次のコード例に、watchFacts関数を示します。この関数は、作業メモリーとの間で追加および削除されたファクトに関する情報を出力します。
次の例に示すように、このwatchFacts関数はファクトがアサートされると==>を出力します。各ファクトにはf-で始まる短い識別子が割り当てられるため、該当するファクトを参照できます。たとえば、アクティブ化にファクトへの参照を含めて、ルール・アクションに渡すことができます。
プログラムはデフォルトのルールセットmainを使用することに注意してください。このルールセットには、enterRoomクラスが含まれています。
RL> watchFacts();
RL> class enterRoom {String who;}
RL> assert(new enterRoom(who: "Rahul"));
==> f-1 main.enterRoom(who : "Rahul")
RL> assert(new enterRoom(who: "Kathy"));
==> f-2 main.enterRoom(who : "Kathy")
RL> assert(new enterRoom(who: "Tom"));
==> f-3 main.enterRoom(who : "Tom")
RL>
showFactsを使用すると、作業メモリー内の現在のファクトを表示できます。次のサンプル・コード例に、Oracle Rules Engineによる初期ファクトf-0のアサートを示します(Oracle Rules Engineはこのファクトを内部的に使用します)。
RL> showFacts(); f-0 initial-fact() f-1 main.enterRoom(who : "Rahul") f-2 main.enterRoom(who : "Kathy") f-3 main.enterRoom(who : "Tom") For a total of 4 facts.
作業メモリーからファクトを削除するには、次の例に示すようにretractを使用します。watchFactsが有効な場合は、ファクトを取り消すと、Oracle Rules Engineは<==を出力します。
RL> watchFacts(); RL> retract(object(2)); <== f-2 main.enterRoom(who : "Kathy") RL> showFacts(); f-0 initial-fact() f-1 main.enterRoom(who : "Rahul") f-3 main.enterRoom(who : "Tom") For a total of 3 facts.
watchActivations関数はOracle Rules Engineを監視し、アジェンダとの間で追加および削除されたルールのアクティブ化に関する情報を出力します。watchRules関数は、ルール起動に関する情報を出力します。
例1-9に、run関数を使用してアクティブ化を起動する方法を示します。最初に入室したRahulへの応対が最後になっていることに注意してください(これは、起動順序のためです)。
注意:
アクティブ化は、関連するファクトが条件を満たさなくなった場合、起動する前にアジェンダから削除できます。
例1-9 WatchActivationsおよびWatchRulesの使用
RL> clear;
RL> class enterRoom {String who;}
RL> assert(new enterRoom(who: "Rahul"));
RL> assert(new enterRoom(who: "Kathy"));
RL> assert(new enterRoom(who: "Tom"));
RL> watchActivations();
RL> rule sayHello {
if (fact enterRoom) {
println("Hello " + enterRoom.who);
}
}
==> Activation: main.sayHello : f-1
==> Activation: main.sayHello : f-2
==> Activation: main.sayHello : f-3
RL> watchRules();
RL> run();
Fire 1 main.sayHello f-3
Hello Tom
Fire 2 main.sayHello f-2
Hello Kathy
Fire 3 main.sayHello f-1
Hello Rahul
RL>
アジェンダにあるルールのアクティブ化を起動する順序付けアルゴリズムを理解するために、ルールセット・スタックについて説明します。各RuleSessionには、1つのルールセット・スタックが含まれています。RuleSessionのルールセット・スタックには、フォーカス・ルールセットと呼ばれる最上位のスタックがあります。このルールセット・スタックには、フォーカス・ルールセット以外のルールセットも含まれます。ルールセット・スタックにルールセットを追加するには、pushRulesetまたはsetRulesetStack組込み関数のいずれかを使用します。ルールセット・スタックのルールセットを管理するには、clearRulesetStack、popRulesetおよびsetRulesetStack関数を使用できます。この場合、ルールセット・スタックのフォーカスは、ルールセット・スタック内の現行の最上位ルールセットになります。
RuleSet Stack
Focus Ruleset --> Top_Ruleset
Next_down_Ruleset
Lower_Ruleset
Bottom_Ruleset
アジェンダにアクティブ化がある場合、Oracle Rules Engineは、run、runUntilHaltまたはstepが実行されるとルールを起動します。Oracle Rules Engineは、次の順序付けアルゴリズムを使用して、アジェンダにあるすべてのアクティブ化からルールのアクティブ化を順に選択します。
Oracle Rules Engineは、フォーカス・ルールセット(つまり、ルールセット・スタックの最上位にあるルールセット)のすべてのルールのアクティブ化を選択します(pushRulesetおよびsetRulesetStack組込み関数を参照)。
フォーカス・ルールセットに関連付けられた複数のアクティブ化内では、ルールの優先度によって起動順序が指定されます。優先度の高いルールのアクティブ化が、優先度の低いルールのアクティブ化よりも先に選択されて起動します(デフォルトの優先度レベルは0です)。
フォーカス・ルールセット内で複数のルールのアクティブ化が同じ優先度の場合は、最後に追加されたルールのアクティブ化から起動します。ただし、複数のアクティブ化がアジェンダに同時に追加された場合、それらのアクティブ化の起動順序は定義されません。
現行フォーカス・ルールセット内のルールのアクティブ化がすべて起動すると、Oracle Rules Engineはルールセット・スタックを削除し、プロセスは現行フォーカスで、ステップ1に戻ります。
R1という名前の一連のルールは、R2という名前の一連のルールよりも前にすべて起動する必要がある場合、次のいずれかの処理方法があります。
1つのルールセットを使用し、R1内のルールの優先度をR2内のルールの優先度よりも高く設定します。
R1とR2の2つのルールセットを使用し、ルールセット・スタックにR2を追加してからR1を追加します。
一般的には、1つのルールセットを使用して優先度を設定するよりも、ルールセット・スタックで2つのルールセットを使用する方が柔軟にルールの起動順序を制御できます。たとえば、R1内のすべてのルールが起動する前に、R1内のルールRが、R2内のあるルールをトリガーする必要がある場合は、R内のreturnアクションによってルールセット・スタックが削除されるため、R2内のルールを起動できます。
ルールの2つのセット間で実行を交互に実行する必要がある場合(たとえば、ファクトを作成するルールとファクトを削除するルール)は、異なる2つの優先度を使用するよりも、2つのルールセットを使用する方がフローの流れが簡単になります。
次のコード例では、keepGaryOutルールの優先度がhighに設定されています。これはsayHelloルールの優先度よりも高くなっています(デフォルトの優先度は0です)。両方のルールのアクティブ化がアジェンダにある場合は、優先度の高いルールが先に起動します。runをコールする前に、sayHelloには2つのアクティブ化がアジェンダにあることに注意してください。keepGaryOutが最初に起動するため、enterRoom(who: "Gary")ファクトが取り消され、これによって対応するsayHelloアクティブ化が削除されます。その結果、1つのsayHelloのみが起動します。
次のコード例に示すルールでは、次の2つのRL Language機能が示されています。
fact演算子(ファクト・セット・パターンとも呼ばれます)は、オプションのvarキーワードを使用し、一致するファクトにバインドする変数(ここでは変数g)を定義します。
作業メモリー内のファクトは、retract関数を使用して削除できます。
RL> final int low = -10;
RL> final int high = 10;
RL> rule keepGaryOut {
priority = high;
if (fact enterRoom(who: "Gary") var g) {
retract(g);
}
}
RL> assert(new enterRoom(who: "Gary"));
==> f-4 main.enterRoom(who: "Gary")
==> Activation: main.sayHello : f-4
==> Activation: main.keepGaryOut : f-4
RL> assert(new enterRoom(who: "Mary"));
==> f-5 main.enterRoom(who: "Mary")
==> Activation: main.sayHello : f-5
RL> run();
Fire 1 main.keepGaryOut f-4
<== f-4 main.enterRoom(who: "Gary")
<== Activation: main.sayHello : f-4
Fire 2 main.sayHello f-5
Hello Mary
RL>
例1-10に、アサートされたenterRoomファクトと一致する条件を含むsayHelloルールを示します。この一致によって、アクティブ化がアジェンダに追加されます。例1-10では、次のRL Languageプログラミング機能を示します。
例1-10 sayHelloルールを伴うenterRoomクラス
RL> class enterRoom { String who; }
RL> rule sayHello {
if ( fact enterRoom ) {
println("Hello " + enterRoom.who);
}
}
RL> assert(new enterRoom(who: "Bob"));
RL> run();
Hello Bob
RL>
ルール起動の順序付けをする場合の重要な詳細に注意してください。
returnアクションを使用すると、ルール起動の動作が変更されます。ルール内のreturnアクションでは、ルールセット・スタックからルールセットが削除されるため、実行は、現在ルールセット・スタックの最上位にあるルールセットからのアジェンダに存在するアクティブ化で続行されます。
ルールの実行がrunまたはstep関数で開始され、returnアクションによってルールセット・スタックから最後のルールセットが削除された場合、制御はrunまたはstep関数のコール元に戻されます。
ルールの実行がrunUntilHalt関数で開始された場合、returnアクションではルールセット・スタックから最後のルールセットは削除されません。最後のルールセットは、残っているアクティブ化がなくなったときにrunUntilHaltで削除されます。その後、Oracle Rules Engineは次のアクティブ化の出現を待機します。アクティブ化が出現すると、ルールセットの起動が再開される前に、ルールセット・スタックに最後のルールセットが配置されます。
ルールの優先度は、指定したルールセット内のルールにのみ適用されます。したがって、別のルールセット内のルールの優先度とは比較できません。
デフォルトでは、有効日の値は、ルール・エンジンによって暗黙的に管理されます。この場合、組込み関数のrunファミリが起動されると、有効日が現在のシステム日付に更新されます。これは、任意のルールが起動される前に実行されます。そのため、新しい有効日は、ルールの起動が開始される前に適用されます。runUntilHaltの場合は、アジェンダにあるルールが0個から1個以上に遷移するたびに、この更新が実行されます。
Oracle Business Rules RL Languageでは、有効開始日、有効終了日およびactiveプロパティのみがルールに適用されます(ルールセットには適用されません)。ルールの有効開始日および有効終了日のプロパティは、ルール内に指定できます。
次に例を示します。
rule myrule2 {
active = true;
effectiveDateForm = Rule.EDFORM_DATETIME:
effectiveStartDate = JavaDate.fromDateTimeString("2008-11-01");
effectiveEndDate = JavaDate.fromDateTimeString("2008-11-16");
if (fact Foo)
{
.
.
}
}
If you use the RuleSession Java API, you can access the effective start and end date.
RL Languageからプロパティを設定するには、長い式またはいくつかの文が必要です。
たとえば、次のようなルールセットがあるとします。
ruleset MyRules {
rule myRule { if fact foo { }}
}
activeプロパティを設定するには、次を使用します。
Rule r = getRuleSession().getRuleset("MyRules").getRule("myRule");
r.setActive(false);
この項では、RL LanguageプログラムとJavaプログラムの統合について説明します。
この項の内容は次のとおりです。
関連項目:
詳細は、『Oracle Business Process Managementによるビジネス・ルールの設計』の「スタンドアロン(非SOA/BPM)シナリオでのルールの使用」を参照してください
例1-11に、単純なBeanのJavaソースを示します。例1-11に示したBean example.Personをjavacコマンドを使用してディレクトリ・ツリーにコンパイルします。
次に、このBeanにアクセスするためのRL Languageコマンドラインの開始方法を示します。
java -classpath $ORACLE_HOME/soa/modules/oracle.rules_11.1.1/rl.jar;BeanPath oracle.rules.rl.session.CommandLine -p "RL> "
BeanPathは提供されているJava Beanクラスへのクラスパス・コンポーネントです。
例1-11 Person BeanクラスのJavaソース
package example;
import java.util.*;
public class Person
{
private String firstName;
private String lastName;
private Set nicknames = new HashSet();
public Person(String first, String last, String[] nick) {
firstName = first; lastName = last;
for (int i = 0; i < nick.length; ++i)
nicknames.add(nick[i]);
}
public Person() {}
public String getFirstName() {return firstName;}
public void setFirstName(String first) {firstName = first;}
public String getLastName() {return lastName;}
public void setLastName(String last) {lastName = last;}
public Set getNicknames() {return nicknames;}
}
例1-12に、example.Personを使用するRL LanguageプログラムをRL Languageコマンドラインで実行する方法を示します。Javaと同様に、import文を使用し、example.PersonのかわりにPersonを使用すると、Personクラスへの参照が可能になります。ルールは、Person Beanクラス、およびそのプロパティとメソッドを参照します。Personファクトを作成するためには、Java Person Beanをアサート(assert)する必要があります。
例1-12では、new演算子を使用して、peopleという名前のPersonオブジェクトの配列を作成します。people配列でfinalが宣言されているため、resetによるpeopleの追加作成はありません。numPeople変数ではfinalが宣言されていないため、resetによってassertPeople関数が再起動し、既存のPersonオブジェクトを使用してPersonファクトが再度アサートされます。
例1-12 Person Beanクラスを使用するルールセット
ruleset main
{
import example.Person;
import java.util.*;
rule hasNickNames
{
if (fact Person(nicknames: var nns) p && !nns.isEmpty())
{
// accessing properties as fields:
println(p.firstName + " " + p.lastName + " has nicknames:");
Iterator i = nns.iterator();
while (i.hasNext())
{
println(i.next());
}
}
}
rule noNickNames
{
if fact Person(nicknames: var nns) p && nns.isEmpty()
{
// accessing properties with getters:
println(p.getFirstName() + " " + p.getLastName() + " does not have nicknames");
}
}
final Person[] people = new Person[] {
new Person("Robert", "Smith", new String[] { "Bob", "Rob" }), // using constructor
new Person(firstName: "Joe", lastName: "Schmoe") // using attribute value pairs
};
function assertPeople(Person[] people) returns int
{
for (int i = 0; i < people.length; ++i) {
assert(people[i]);
}
return people.length;
}
int numPeople = assertPeople(people);
run();
}
Java Beansをファクトとして使用する場合は、次の点に注意してください。
fact演算子では、一致するパターンを含めたり、Beanプロパティを取得できます。プロパティは、Beanクラスのgetterおよびsetterメソッドによって定義されます。
new演算子では、デフォルトの引数なしのコンストラクタを起動した後にプロパティ値を設定するパターンを含めたり、引数をユーザー定義コンストラクタに渡すことができます。
factおよびnew演算子の外側では、getterおよびsetterメソッドを使用するか、あるいはフィールドのようにプロパティ名を使用して、Beanプロパティを参照または更新できます。
Beanに同じ名前のプロパティとフィールドがある場合、そのフィールドはRL Languageでは参照できません。
例1-12を実行した後に、同じRuleSessionを使用して例1-13を実行した場合、出力は例1-12の結果と同じになります(両方のpersonファクトが再アサートされます)。
注意:
RL Languageコマンドライン・インタプリタは、起動時(およびclearコマンドの使用時)にRuleSessionを内部的に作成します。
例1-13 RuleSessionでのresetの使用
reset(); run();
Javaプログラムでは、RuleSessionインタフェースを使用して、ルールセットの実行、Javaオブジェクトを引数として渡すRL Language関数の起動、およびRL Languageのwatchおよびprintln出力のリダイレクトを行うことができます。例1-14および例1-15に、「hello world」を出力するRuleSessionを使用するJavaプログラムのフラグメントを示します。多くのJavaプログラムのフラグメントと同様に、これらの例もRL Languageプログラムとして有効です。
RL Language環境では、複数のルール・セッションを使用できます。各ルール・セッションは複数のスレッドで使用できますが、ルールは一度に1つのスレッドで起動します。
各ルールのRuleSessionには、ファクトとルールの独自のコピーがあります。ファクトをJavaオブジェクトから作成するには、次のようなコールを使用します。
rs.callFunctionWithArgument("assert", Object;);
ルール、関数またはRL Languageクラスを作成するには、ルールセットを含む文字列を定義し、executeRulesetメソッドを使用します。
例1-14 callFunctionWithArgumentを使用するRuleSessionオブジェクトの使用
import oracle.rules.rl.*;
try {
RuleSession rs = new RuleSession();
rs.callFunctionWithArgument("println", "hello world");
} catch (RLException rle) {
System.out.println(rle);
}
例1-15 executeRulesetを使用するRuleSessionの使用
import oracle.rules.rl.*;
try {
RuleSession rs = new RuleSession();
String rset =
"ruleset main {" +
" function myPrintln(String s) {" +
" println(s);" +
" }" +
"}";
rs.executeRuleset(rset);
rs.callFunctionWithArgument("myPrintln", "hello world");
} catch (RLException rle) {
System.out.println(rle);
}
この項では、RL Languageを使用して次のパズルを解く例を示します。
50個の硬貨を使用して合計金額を1ドル50セントにする方法は何通りあるか?
このパズルを解くルール・プログラムには、ルールベース・プログラミングの重要なポイントが含まれています。知識表現、つまり、選択するファクト・クラスが設計上の重要な問題になる可能性があります。ルールで照合および処理するためにデータを便利なフォーマットに整えるには、多くの場合、手続きコードを記述することが有効です。
この例を使用するには、最初に、例1-16に示したRL Languageプログラムをcoins.rlという名前のファイルにコピーします。このプログラムは、RL Languageコマンドラインからincludeコマンドを使用してインクルードできます。coinsプログラムをインクルードする前に、次のようにclear;コマンドを使用して、現行のルール・セッション内をすべて消去します。
RL> clear; RL> include file:coins.rl; RL>
次のコード例に示したデバッグ関数は、硬貨カウンタに関する、硬貨カウントのサンプル・ファクト、アクティブ化およびルールを示しています。すべてのファクトはアサートされ、すべての解答のアクティブ化がアジェンダに配置されます。ファクトはpopulate_factsで生成されると、ルール条件と照合され、find_solutionでは一致が出力されることに注意してください。
RL> watchFacts(); RL> watchActivations(); RL> watchRules(); RL> reset(); RL> showActivations(); RL> run(); The rule is fired for each activation, printing out the solutions RL>
例1-16では、coinCount、totalAmountなどのグローバル変数定義の先頭にあるキーワードfinalによって、Javaと同様に、変数が定数としてマークされます。ルール条件の定数は参照できますが、変数は参照できません。
RL Languageでは、すべての変数を初期化する必要があります。final変数を初期化する式は、変数の定義時に1回評価されます。final以外の変数を初期化する式は、変数の定義時およびreset関数がコールされるたびに評価されます。reset関数によってすべてのファクトが作業メモリーから取り消されるため、初期ファクトは、グローバル変数を初期化する式でアサート(assert)することをお薦めします。これによって、resetがコールされるとファクトが再度アサートされます。
例1-16に、グローバル変数を初期化する式の使用方法を示します。initializedグローバル変数は、populate_facts関数を使用して初期化されます。この関数は、resetがコールされるたびに再実行されます。このpopulate_facts関数には、forループ内にネストされたwhileループがあります。このforループは、硬貨の額面を示す文字列の配列に対して反復されます。whileループは、硬貨の額面ごとに、カウントと合計(合計金額は$1.50を超えません)を表すファクトをアサートします。たとえば、50セント硬貨の場合は次のようになります。
coin(denomination "half-dollar", count:0, amount:0) coin(denomination "half-dollar", count:1, amount:50) coin(denomination "half-dollar", count:2, amount:100) coin(denomination "half-dollar", count:3, amount:150)
このようなファクトを作業メモリーで使用すると、カウントの合計をcoinCountに、金額の合計をtotalAmtにする必要がある条件を使用して、ルールfind_solutionが各額面に対して照合されます。find_solutionのアクティブ化は、run関数によって起動します。
例1-16 硬貨カウントのプログラム・ソース
final int coinCount = 50;
final int totalAmt = 150;
final String[] denominations = new String[]
{"half-dollar" , "quarter", "dime", "nickel", "penny" };
class coin {
String denomination;
int count;
int amount;
}
function populate_facts() returns boolean
{
for (int i = 0; i < denominations.length; ++i) {
String denom = denominations[i];
int count = 0;
int total = 0;
int amount = 0;
if (denom == "half-dollar" ) { amount = 50; }
else if (denom == "quarter" ) { amount = 25; }
else if (denom == "dime" ) { amount = 10; }
else if (denom == "nickel" ) { amount = 5; }
else { amount = 1; }
while (total <= totalAmt && count <= coinCount)
{
assert(new coin(denomination: denom,
count : count,
amount : total));
total += amount;
count ++;
}
}
return true;
}
boolean initialized = populate_facts();
rule find_solution
{
if(fact coin(denomination: "penny") p
&& fact coin(denomination: "nickel") n
&& fact coin(denomination: "dime") d
&& fact coin(denomination: "quarter") q
&& fact coin(denomination: "half-dollar") h
&& p.count + n.count + d.count + q.count + h.count == coinCount
&& p.amount + n.amount + d.amount + q.amount + h.amount == totalAmt)
{
println("Solution:"
+ " pennies=" + p.count
+ " nickels=" + n.count
+ " dimes=" + d.count
+ " quarters=" + q.count
+ " half-dollars=" + h.count
);
}
}
run();