Drools 5.0 入門 3B (no-loop属性)

ループの例

 Droolsのルール実行方法である前向き推論は、プログラミングパラダイムの視点から見ると、「データ駆動」というある種独特の感覚があります。Prologに代表される後向き推論はパターンマッチングということこそ同じですが、いわゆる「ゴール駆動(目標駆動)」でやはりちょっと違う感覚ですし、ましてや手続き型や最近流行の関数型のプログラミングとも違います。ルールエンジンの最近一般的になっている使い方・・・Validationチェックやレコメンデーションなど・・・では、データ駆動のプログラミングパラダイムを深く意識する場面はそれほどないかもしれませんが、プログラミングのひとつのあらたな視点を提供するという意味でもう少しルールプログラムの動きを追っていきましょう。また、その中で同時に salience や no-loop などに代表されるルールの属性(attribute)も見ていきましょう。

 

サンプルプログラム

 前の記事で、6000円の買い物をしたときに500円割引になって5500円の売り上げとなったことを見ました。さて、ここでルールの動きを見るために前の記事で使ったルールの一部をちょっとだけ修正して動かしてみましょう。

DroolsTest1.drl (一部修正)

package jp.co.iluminado.example;
 
import jp.co.iluminado.example.DroolsTest1.Sales;

rule “Discount”
    when
        $s : Sales( // status == Sales.NOT_APPLIED, →コメントにした
                   $salesValue : sales >= 5000 )
    then
        $s.setSales( $salesValue – 500 );
        $s.setStatus( Sales.APPLIED );
        update( $s );
        System.out.println( $s.getSales() );
end

when節で、Salesファクトの条件から status の条件をコメントアウトします。すると売り上げ(sales)だけの条件でパターンマッチが起こるはずです。実行してみましょう。

ループの例

5500、5000、4500となってそこで止まっています(上の実行結果では、メインプログラム(DroolsTest1.java)の方のDebug用の2つのEventListnerもコメントアウトしたのでSystem.errの出力は出ていません)。 どうやらDiscountのルールが3回発火したようです。ワーキングメモリの状態に沿って実行状況を追ってみると

 1.最初にSalesファクトをワーキングメモリにインサートすると、

      Sales(status : NOT_APPLIED, sales : 6000)

 2.Discountルールのwhen節はファクトにマッチするので、最初のルールの実行をして

      Sales(status : APPLIED, sales : 5500)

  3.Discountルールのwhen節は再びファクトにマッチするので、2回目のルールの実行をして

      Sales(status : APPLIED, sales : 5000)

  4.Discountルールのwhen節は再びファクトにマッチするので、3回目のルールの実行をして

      Sales(status : APPLIED, sales : 4500)

  5.Discountルールのwhen節がマッチしなくなったので実行終了

 となります。

 このようにDroolsルールエンジンにおいては、ワーキングメモリ中のファクトがルールとマッチする間、常にルールが発火するようになっています。これがファクトの状態がルールを発火させる・・・「データ駆動」の名前のゆえんです。

 

ルールのno-loop属性

  ルール実行の原則は、「ファクトがルールとマッチする間、常にルールは発火する」のですが、Droolsでは、ルールに属性を与えてルール発火の制御をすることができるようになっています。no-loop属性がそのひとつです。ルールの属性はDroolsではwhen節の前に書きます。さきほどのルールファイルにno-loop属性を加えてみましょう。

DroolsTest1.drl (一部修正)

package jp.co.iluminado.example;
 
import jp.co.iluminado.example.DroolsTest1.Sales;

rule “Discount”
    no-loop  // ルール属性を加えた
    when
        $s : Sales( // status == Sales.NOT_APPLIED, →コメントのまま
                   $salesValue : sales >= 5000 )
    then
        $s.setSales( $salesValue – 500 );
        $s.setStatus( Sales.APPLIED );
        update( $s );
        System.out.println( $s.getSales() );
end

 no-loop 属性はその名が示すとおり、そのルールが単独でループを起こさないようにする・・・1回だけ発火するようにする属性です。ちょっと実行してみましょう。

no-loopの例

5500だけが表示され、ルールは1回しか発火していないのがわかります。このようにno-loop属性はルール単独でループをなさないように制限するルール属性です。

ここで「単独」というのが少々ミソで、複数のルールが互いにファクトを更新しあってループを作ってしまうような場合には有効でないので注意ください。

 

 さて、以上Drools のルール実行の様子を見てきましたが、実際、前向き推論のデータ駆動のプログラミングでは繰り返しのループを作りやすく、それが良いところでも悪いところでもあります。 注意をしていないと意図せずループを作ってしまうことがあり、全然結果が返ってこないなあと思っていると無限ループに入っていたりということも・・・。最近のルールエンジンでは、そういった罠に関しては良くできていて、こういったループ制御の仕組みはDroolsに限らずさまざまなツールで配慮されています。

 ただ、このループ制御のしくみですが、むやみに使うと逆に、「ファクトがルールを起動し、それによって更新されたファクトがさらなるルールを起動していく・・・」というデータ駆動のルール実行の良さを消すことにもなりかねません。したがって、ループ制御のしくみは、むしろ意図しない無限ループを回避するためのセーフティネットとして考えておいた方が良いのではないかと思います。実際のルールの実行の制御としては、上でコメントでつぶしてしまった仕様として意味のある status などを意識して有効に使っていく方が、ずっとわかりやすいプログラムが書けるのではないでしょうか。