プログラミングパラダイムとしてのルールベース

サグラダファミリアBRMSの多くが依拠しているルールを用いたプログラミング(ルールベースプログラミング)について、最近はなかなか正面からの解説が少ないように思います。これは、最近のBRMSの適用例の多くが入力チェックや、査定、不正検知など、あまりルールベースのプログラミングのプログラミング上の特徴が表れてこない例が多く、あえてルールベースプログラミングを強調することもないということだからでしょうか。

(なお、ここで言う「ルール」を用いたプログラミングとは、通常のプログラミング言語で言うところの if 文を手続き的に整理した「ルール」ではなく、いわゆるプロダクションシステムにおける「ルール」、すなわちアルゴリズム的にはRete(およびその改良版)、DeTI、(最近では)Phreakなどを採用しているルールエンジンの「ルール」、を用いたプログラミングのことになります)

とはいえ、やはり通常のプログラミング言語 -手続き型- の延長としてルールベースプログラミングを理解してしまうと、時によってルールベースプログラミングの良さを引き出せないままBRMSを使ってしまったり…ということがありそうな例を (かなり前ですが) 見聞きしたのでちょっとここで紹介したいと思います。

例題
ある入力画面で、氏名の入力欄(たとえば保険金受取人)が
10段並んでいる。入力チェックとして、違う欄に同じ氏名が
入力されていたらエラーとしたい。

これ、通常のプログラミング言語で書こうとすると、二重の for 文 を回して同じ氏名が入力されていないかチェックするということになると思います。ところが、ルールベースプログラミングですと 「違う欄に同じ氏名が入力されていたらエラー」 ということをそのまま一つのルールとして実装するだけでプログラミングが完了します。

rule "重複チェックルール"
    when
        c1 : Column( id1 : id_No , name1 : name != "");
           // 名前が空白のものは対象外にする
        c2 : Column( id2 : id_No > id1, name == name1 );
           // 欄は違う (id_Noが違う)  けれども 名前は同じ。
    then
        System.out.println("重複がありました。名前は" + name1
                             + "id=" + c1.getId_No()
                             + " と id=" + c2.getId_No()
                             + "です。");
end

なお、ここで名前欄を表す

public class Column {
    private int id_No;
    private String name;
    // getter, setter  ほか
}

というクラスを用いています。

このルールを以下のデータ

Column [id_No=1, name=野中]
Column [id_No=2, name=伊丹]
Column [id_No=3, name=伊丹]
Column [id_No=4, name=大前]
Column [id_No=5, name=嶋口]
Column [id_No=6, name=野中]
Column [id_No=7, name=]
Column [id_No=8, name=野中]
Column [id_No=9, name=]
Column [id_No=10, name=]

に適用してみると、

重複がありました。名前は野中id=1 と id=6です。
重複がありました。名前は野中id=1 と id=8です。
重複がありました。名前は伊丹id=2 と id=3です。
重複がありました。名前は野中id=6 と id=8です。

といった結果になります。

このようにルールベースのプログラミングでは、繰り返しを明示的に書くことは多くの場合必要ありません。もちろん、ルールベースでもカウンタにあたる属性あるいはFactなどを作って繰り返しを表現することは可能なのですが、概して繰り返しを書かずとも望む結果が得られる場合が多いです。なので、もし繰り返しが必要になりそうなときは本当にその繰り返しが必要かどうか自問自答してみるべきです。

この繰り返しを書く必要がないというところは、次のルールベースプログラムの実行原則のひとつに拠っています。

条件に合うものは「全部」実行する。実行するものがなくなったら終了。

ここの「全部」というところは、通常の手続き型のプログラミング言語では for や while などや、さらにたとえば iterator を使って繰り返しも含めたプログラミングすることになるでしょうが、この繰り返しは、ルールベースでは上記にあげた実行原則に見るように、すでに実行の機構として言語に含まれてしまっています。したがってルールベースプログラミングでは、「何をするか」というルールの記述のみを行えばよいことになります。

翻って、人間の職場の作業依頼/指示について考えてみましょう。たとえば、書類の束を渡して、「XXのチェックしておいて」とか「チェックして印鑑お願いします」とか…「何を」行うかのみの依頼/指示だけで、手続き的に「上から順に」チェックするとか、「全部」チェックするとかの指示はしないのが普通でしょう。

ルールベースプログラミングの基本は、「どのように」処理を実行するかの記述を省き、単刀直入に「何をするか」ということをルールで記述していくところにあります。これがまさにルールベースプログラミングが宣言的プログラミングであるといわれる所以であり、また、ルールベースのプログラムが人間の感覚に近いと言われる所以でもあります。

上の例のルールを見てみましょう。処理そのものを追ってみると、

条件部の1行目(c1にあたるところ)は、Column( というところだけを見ると、データとしてあげた10個のColumnファクトすべてがマッチする可能性がありますが、name != “” というところまで見ていくと、10個のファクトのうち id_No=7,9 のファクトは対象外となります。残りの8個のファクトが c1 にマッチするということになります。
次に2行目(c2)。id_Noがc1のid_Noより大きく(違う != という条件でもよいのですが、同じ結果の組が2つ出てきてしまう-たとえばid=1とid=6の組、id=6とid=1の組の2つ-ので不等号の条件にしています…念のため)、名前が同じという条件にすると、c1とc2の組み合わせで、上記に示したような結果が得られるというわけ。

ですが、ルールの条件部を先入観を持たずに見てみると、単純に例題の「違う欄に同じ氏名が入力されていたら」を記述しただけ (注1)ということがわかると思います。

以上、例題を通して、ルールベースプログラミングの特徴 (の一つ) を見てきました。最初にも書いたようにBRMSの最近の適用例はあまりルールベースプログラミングの特徴が表れにくいものが多いのですが、例題としてもあげたように、ところどころルールベース的に書くべきところも散見されるので今回、とりあげてみました。

さて、これを言ってしまうと、場合によってはルールベースの敷居の高さを感じてしまう方もおられるかもしれませんが、そもそもルールベースプログラミングは、通常の手続き型のプログラミングとはパラダイムが違います。ただ、いわゆる関数型のパラダイムの(Haskellの)モナドとか、論理型で言えばPrologのカットオペレータとか、わかりにくい概念というものはなく(注2)、「if-thenルールの条件にマッチするものを実行する」という人間の素朴な直感にマッチしたパラダイムなのでそれほど身構える必要はありません。むしろ手続き型のように、「どのように」実行するかを書いていく必要がない分、ルールベースでは単刀直入に「何を行うか」を書けるのでわかりやすいとも言えましょう。もっとも、だからこそBRMSが流行っているのだとは思いますが。

(注1) 名前が空白でないという追加の条件は加えています。
(注2) 強いて言えば競合解消戦略がわかりにくいかと思いますが、最近はあまり競合解消戦略に依存するようなルールの書き方はしないので、あまり問題にはならないでしょう。

コメント

  1. […] が複雑になると本来の仕様が処理手順に埋もれてしまうということになります。たとえば前回の記事などのようにデータに親子関係があり、複数の子の間での制約など if-then ルールとして […]

  2. […] この前の更新からずいぶんと間があいてしまいましたが、最近、ビジネスルールというか、「ルール」というものの認識についてどうも気になることがあったので、考え方としては前回と重なる部分も多いかと思いましたが、再び記事を書いてみました。またこれに限らず、ぼちぼちと記事を書いていきたいと思っております。 […]

タイトルとURLをコピーしました