この章では、Oracle Business Rules RL Language(RL Language)の概念について次の各項で説明します。
Oracle Business Rules環境は、rl.jar
で提供されるクラスによってJVMコンテナまたはJ2EEコンテナに実装されます。RL Languageコマンドライン・インタフェースは、次のコマンドを使用して開始します。
java -jar RuleDir/lib/rl.jar -p "RL> "
RuleDirは、Oracle Business Rulesインストール用のパスです。–p
オプションはプロンプトを指定します。
RL Languageコマンドライン・インタフェースによって、Oracle Business RulesのRuleSessionにアクセスできます。このRuleSessionは、JavaプログラマがJavaアプリケーションでRL Languageにアクセスするための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コンポーネントで、ファクトを参照する複数の条件式で構成されています。
次の例では、条件式がファクト(Driver
インスタンスのd1
)を参照し、次に、そのファクトのデータ・メンバーage
が16未満であることをテストします。
if (fact Driver d1 && d1.age < 16)
例1-3に、RL Languageで記述された完全なルールを示します(このルールには、1つのルール条件と1つのルール・アクションが含まれています)。
Oracle Rules Engineは、ルールの条件式がtrueとなるファクトの組合せがあると、ルールをアクティブ化します。いくつかの点で、ルール条件は、Oracle Rules Engineで使用可能なファクトへの問合せに類似しています。この場合、問合せで戻されたすべての行に対して、ルールがアクティブ化されます。
注意: ルールのアクティブ化とルールの起動は異なります。 |
ルール・アクションは、すべてのルール条件が満たされた場合にアクティブ化されます。ルールの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
クラスがクラスパスで使用可能であることを前提にしています)。
アサートされた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プログラムの記述とデバッグに有用です。
内容は次のとおりです。
例1-9に、watchFacts
関数を示します。この関数は、作業メモリーとの間で追加および削除されたファクトに関する情報を出力します。
例1-9に示すように、このwatchFacts
関数はファクトがアサートされると==>
を出力します。各ファクトにはf-
で始まる短い識別子が割り当てられるため、該当するファクトを参照できます。たとえば、アクティブ化にファクトへの参照を含めて、ルール・アクションに渡すことができます。
例1-9では、デフォルト・ルールセットmain
がプログラムで使用されていることに注意してください。このルールセットには、enterRoom
クラスが含まれています。
例1-9 enterRoomファクトでのwatchFactsの使用
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
を使用すると、作業メモリー内の現在のファクトを表示できます。例1-10に、Oracle Rules Engineによる初期ファクトf-0
のアサートを示します(Oracle Rules Engineはこのファクトを内部的に使用します)。
例1-10 作業メモリー内のファクトの表示
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.
作業メモリーからファクトを削除するには、例1-11に示すようにretractを使用します。watchFacts
が有効な場合は、ファクトを取り消すと、Oracle Rules Engineは<==
を出力します。
watchActivations
関数はOracle Rules Engineを監視し、アジェンダとの間で追加および削除されたルールのアクティブ化に関する情報を出力します。watchRules
関数は、ルール起動に関する情報を出力します。
例1-12に、run
関数を使用してアクティブ化を起動する方法を示します。最初に入室したRahulへの応対が最後になっていることに注意してください(これは、起動順序のためです)。
注意: アクティブ化は、関連するファクトが条件を満たさなくなった場合、起動する前にアジェンダから削除できます。 |
例1-12 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関数を使用できます。この場合、ルールセット・スタックのフォーカスは、ルールセット・スタック内の現行の最上位ルールセットになります(例1-13を参照)。
例1-13 ルールセット・スタック - 図
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つのルールセットを使用する方がフローの流れが簡単になります。
例1-14では、keepGaryOut
ルールの優先度がhighに設定されています。これは、sayHello
ルールの優先度よりも高くなっています(デフォルトの優先度は0です)。両方のルールのアクティブ化がアジェンダにある場合は、優先度の高いルールが先に起動します。run
をコールする前に、sayHello
には2つのアクティブ化がアジェンダにあることに注意してください。keepGaryOut
が最初に起動するため、enterRoom(who: "Gary")
ファクトが取り消され、これによって対応するsayHello
アクティブ化が削除されます。その結果、1つのsayHello
のみが起動します。
例1-14に示すルールでは、次の2つのRL Language機能が示されています。
fact
演算子(ファクト・セット・パターンとも呼ばれます)は、オプションのvar
キーワードを使用し、一致するファクトにバインドする変数(ここでは変数g
)を定義します。
作業メモリー内のファクトは、retract
関数を使用して削除できます。
例1-14 keepGaryOutルールでのルール優先度の使用
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-15に、アサートされたenterRoom
ファクトと一致する条件を含むsayHello
ルールを示します。この一致によって、アクティブ化がアジェンダに追加されます。例1-15では、次のRL Languageプログラミング機能を示します。
Oracle Rules Engineは、作業メモリーの状態に変化があると、ファクトをすべてのルールのルール条件(fact-set-condition)と照合します。したがって、ファクトがアサートされるのがルールの定義前か後かは関係ありません。
run関数は、アジェンダにあるすべてのアクティブ化を処理します。runのコール前にアジェンダのアクティブ化が処理されることはありません。
例1-15 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は次のアクティブ化の出現を待機します。アクティブ化が出現すると、ルールセットの起動が再開される前に、ルールセット・スタックに最後のルールセットが配置されます。
ルールの優先度は、指定したルールセット内のルールにのみ適用されます。したがって、別のルールセット内のルールの優先度とは比較できません。
この項では、RL LanguageプログラムとJavaプログラムの統合について説明します。内容は次のとおりです。
関連項目: 『Oracle Business Rulesユーザーズ・ガイド』の第3章の「ルールの起動」 |
例1-16に、単純なBeanのJavaソースを示します。例1-16に示したBean example.Person
をjavac
コマンドを使用してディレクトリ・ツリーにコンパイルします。
次に、このBeanにアクセスするためのRL Languageコマンドラインの開始方法を示します。
java -classpath RuleDir/lib/rl.jar;BeanPath oracle.rules.rl.session.CommandLine -p "RL> "
BeanPathは、提供されているJava Beanクラスへのクラスパス・コンポーネントです。
例1-16 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-17に、example.Person
を使用するRL LanguageプログラムをRL Languageコマンドラインで実行する方法を示します。Javaと同様に、import
文を使用し、example.Person
のかわりにPerson
を使用すると、Person
クラスへの参照が可能になります。ルールは、Person
Beanクラス、およびそのプロパティとメソッドを参照します。Person
ファクトを作成するためには、Java Person
Beanをアサート(assert
)する必要があります。
例1-17では、new
演算子を使用して、people
という名前のPerson
オブジェクトの配列を作成します。people
配列でfinalが宣言されているため、reset
によるpeople
の追加作成はありません。numPeople
変数ではfinal
が宣言されていないため、reset
によってassertPeople
関数が再起動し、既存のPerson
オブジェクトを使用してPerson
ファクトが再度アサートされます。
例1-17 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 Beanをファクトとして使用する場合は、次の点に注意してください。
fact
演算子では、一致するパターンを含めたり、Beanプロパティを取得できます。プロパティは、Beanクラスのgetterおよびsetterメソッドによって定義されます。
new
演算子では、デフォルトの引数なしのコンストラクタを起動した後にプロパティ値を設定するパターンを含めたり、引数をユーザー定義コンストラクタに渡すことができます。
fact
およびnew
演算子の外側では、getterおよびsetterメソッドを使用するか、あるいはフィールドのようにプロパティ名を使用して、Beanプロパティを参照または更新できます。
Beanに同じ名前のプロパティとフィールドがある場合、そのフィールドはRL Languageでは参照できません。
例1-17を実行した後に、同じRuleSession
を使用して例1-18を実行した場合、出力は例1-17の結果と同じになります(両方のperson
ファクトが再アサートされます)。
注意: RL Languageコマンドライン・インタプリタは、起動時(およびclear コマンドの使用時)にRuleSession を内部的に作成します。 |
Javaプログラムでは、RuleSession
インタフェースを使用して、ルールセットの実行、Javaオブジェクトを引数として渡すRL Language関数の起動、およびRL Languageのwatch
およびprintln
出力のリダイレクトを行うことができます。例1-19および例1-20に、「hello world」を出力するRuleSession
を使用するJavaプログラムのフラグメントを示します。多くのJavaプログラムのフラグメントと同様に、これらの例もRL Languageプログラムとして有効です。
RL Language環境では、複数のルール・セッションを使用できます。各ルール・セッションは複数のスレッドで使用できますが、ルールは一度に1つのスレッドで起動します。
各ルールのRuleSessionには、ファクトとルールの独自のコピーがあります。ファクトをJavaオブジェクトから作成するには、次のようなコールを使用します。
rs.callFunctionWithArgument("assert", Object;);
ルール、関数またはRL Languageクラスを作成するには、ルールセットを含む文字列を定義し、executeRuleset
メソッドを使用します。
例1-19 callFunctionWithArgumentを使用するRuleSessionオブジェクトの使用
import oracle.rules.rl.*; try { RuleSession rs = new RuleSession(); rs.callFunctionWithArgument("println", "hello world"); } catch (RLException rle) { System.out.println(rle); }
例1-20 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-22に示したRL Languageプログラムをcoins.rl
という名前のファイルにコピーします。このプログラムは、RL Languageコマンドラインからinclude
コマンドを使用してインクルードできます。coinsプログラムをインクルードする前に、次のようにclear;
コマンドを使用して、現行のルール・セッション内をすべて消去します。
RL> clear; RL> include file:coins.rl; RL>
例1-21に示したデバッグ関数は、硬貨カウンタに関する、硬貨カウントのサンプル・ファクト、アクティブ化およびルールを示しています。すべてのファクトはアサートされ、すべての解答のアクティブ化がアジェンダに配置されます。ファクトはpopulate_facts
で生成されると、ルール条件と照合され、find_solution
では一致のみが出力されることに注意してください。
例1-21 coinsプログラム例でのデバック関数の使用
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-22では、coinCount
、totalAmount
などのグローバル変数定義の先頭にあるキーワードfinal
によって、Javaと同様に、変数が定数としてマークされます。ルール条件の定数は参照できますが、変数は参照できません。
RL Languageでは、すべての変数を初期化する必要があります。final
変数を初期化する式は、変数の定義時に1回評価されます。final以外の変数を初期化する式は、変数の定義時、およびreset
関数がコールされるたびに評価されます。reset
関数によってすべてのファクトが作業メモリーから取り消されるため、初期ファクトは、グローバル変数を初期化する式でアサート(assert
)することをお薦めします。これによって、reset
がコールされるとファクトが再度アサートされます。
例1-22に、グローバル変数を初期化する式の使用方法を示します。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-22 硬貨カウントのプログラム・ソース
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();