最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

← 先頭のページに戻る

デカルト言語による論理的なA.I: 自然言語による論理

(注:この例題は、リリース0.10.0以降のバージョンでないと動作しません。)

こんな感じで、会話するA.I(人工知能)を創りましょう。


#  aは、bである。
わかりました。
#  bは、cだ。
わかりました。
#  cならば、dだ。
わかりました。
#  cは、e。
わかりました。
#  dは,  fである。
わかりました。
#  eだったら, aです。
わかりました。
#  aは、f?
はい
なぜならば、 aならば、bである。 bならば、cである。 cならば、dである。 dならば、fであ
る。
#  dは, eか?
わかりません。
#  cは、bか?
はい
なぜならば、 cならば、eである。 eならば、aである。 aならば、bである。
#  cならば?

 cならば、eである。
 cならば、eである。 eならば、aである。
 cならば、eである。 eならば、aである。 aならば、bである。
 cならば、dである。
 cならば、dである。 dならば、fである。
#  aは?

 aならば、bである。
 aならば、bである。 bならば、cである。
 aならば、bである。 bならば、cである。 cならば、eである。
 aならば、bである。 bならば、cである。 cならば、dである。
 aならば、bである。 bならば、cである。 cならば、dである。 dならば、fである。
#  cであるのは?

 eならば、aである。 aならば、bである。 bならば、cである。
 bならば、cである。
 aならば、bである。 bならば、cである。

#の行が、人間が入力した文です。基本的な3段論法の文です。

デカルト言語のこのプログラムは、入力された論理的な関係を記憶します。

また、記憶した論理関係によって、人間からの質問にも的確に回答しています。

どうでしょうか?

デカルト言語で上記のような応答をするプログラムのソースを以下に示します。


::<推論エンジン
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>;

        <推論 #x #x _ ()>;
        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;
>;



<三段論法>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        (  "、" | "," | "," )
        <* #y>
        ["である" | "です" | "だ"]
        ( "。" )                ::sys <concat #line (#x  "ならば、" #y "である。")>
                                ::推論エンジン <記憶 #line #x #y>
        ;

<質問>
        <* #x>
        ( "になるのは" | "であるには" | "であるのは" )
        ( "?" | "?" )          ::推論エンジン <全推論 _ #x>

        ;

<質問>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        ( "?" | "?" )          ::推論エンジン <全推論 #x _>
        ;


<質問 #p>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        (  "、"
         | ","
         | ","
        )
        <* #y>
        [ "であるか" | "ですか" | "か" ]
        ( "?" | "?" )           ::推論エンジン <推論 #x #y #p>
                                 ::sys <is #res "はい">
        ;

<会話 #res>
        (
                <質問 #p>       ::sys <is #res "はい">
                                        <print "はい">
                                        <printf "なぜならば、">
                                        <printlist #p>
         |
                <質問>          ::sys <is #res "はい">
         |
                <三段論法>      <print "わかりました。">
                                ::sys <is #res "わかりました。">
         |
                                <print "わかりません。">
                                ::sys <is #res "わかりません。">
        )
        ;

? { ::sys <getline #l <print "# " #l> <会話 #x>> };

この73行ほどの短いプログラムによって、論理的な推論を行い、自然言語で会話をすることができます。

このプログラムは、logic.carという名前で保存します。 日本語の文字コードが含まれているので、デカルト言語を動作させるOSの文字コードにあわせたコードで保存してください。 Linuxの場合はUTF-8で、Windowsの場合はSJISで保存すればよいでしょう。

入力は以下のような文で行います。


aは、bである。
bは、cだ。
cならば、dだ。
cは、e。
dは,  fである。
eだったら, aです。
aは、f?
dは, eか?
cは、bか?
cならば?
aは?
cであるのは?

仮にkaiwa.txtと名付けて保存しましょう。

実行するには以下のようにします。


descartes logic.car <kaiwa.txt

実行結果は冒頭に示したようになります。

もう一つ例を示します。 a,b,cの関係では面白くないので、「風が吹くならば、桶屋が儲かるか?」を試します。

以下のような入力ファイルを用意しkaze.txtの名前で保存します。


風が吹くならば、ほこりが舞う。
ほこりが舞うならば、街が汚れる。
街が汚れるならば、清掃しなければならない。
清掃しなければならないならば、桶が必要である。
桶が必要ならば、桶屋が儲かる。
風が吹くならば、桶屋が儲かるか?
風が吹くならば?
桶屋が儲かるであるには?
桶が必要になるのは?

実行してみましょう。


descartes logic.car <kaze.txt
#  風が吹くならば、ほこりが舞う。
わかりました。
#  ほこりが舞うならば、街が汚れる。
わかりました。
#  街が汚れるならば、清掃しなければならない。
わかりました。
#  清掃しなければならないならば、桶が必要である。
わかりました。
#  桶が必要ならば、桶屋が儲かる。
わかりました。
#  風が吹くならば、桶屋が儲かるか?
はい
なぜならば、 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるであ
る。 街が汚れるならば、清掃しなければならないである。 清掃しなければならないならば、
桶が必要である。 桶が必要ならば、桶屋が儲かるである。
#  風が吹くならば?

 風が吹くならば、ほこりが舞うである。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。 街が汚れ
るならば、清掃しなければならないである。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。 街が汚れ
るならば、清掃しなければならないである。 清掃しなければならないならば、桶が必要である
。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。 街が汚れ
るならば、清掃しなければならないである。 清掃しなければならないならば、桶が必要である
。 桶が必要ならば、桶屋が儲かるである。
#  桶屋が儲かるであるには?

 桶が必要ならば、桶屋が儲かるである。
 清掃しなければならないならば、桶が必要である。 桶が必要ならば、桶屋が儲かるである。
 街が汚れるならば、清掃しなければならないである。 清掃しなければならないならば、桶が
必要である。 桶が必要ならば、桶屋が儲かるである。
 ほこりが舞うならば、街が汚れるである。 街が汚れるならば、清掃しなければならないであ
る。 清掃しなければならないならば、桶が必要である。 桶が必要ならば、桶屋が儲かるであ
る。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。 街が汚れ
るならば、清掃しなければならないである。 清掃しなければならないならば、桶が必要である
。 桶が必要ならば、桶屋が儲かるである。
#  桶が必要になるのは?

 清掃しなければならないならば、桶が必要である。
 街が汚れるならば、清掃しなければならないである。 清掃しなければならないならば、桶が
必要である。
 ほこりが舞うならば、街が汚れるである。 街が汚れるならば、清掃しなければならないであ
る。 清掃しなければならないならば、桶が必要である。
 風が吹くならば、ほこりが舞うである。 ほこりが舞うならば、街が汚れるである。 街が汚れ
るならば、清掃しなければならないである。 清掃しなければならないならば、桶が必要である
。
result --
        {
        (::sys <getline Undef30 <print #  Undef30> <会話 Undef31>>)
        }
-- true

どうでしょうか?A.Iとのやりとりに見えるでしょうか?

プログラムの詳細については、次項から説明します。

1. 推論エンジン

推論エンジンは、「推論エンジン」オブジェクトで実装されます。


::<推論エンジン
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>;

        <推論 #x #x _ ()>;

        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;
>;

最初の「::<推論エンジン」がオブジェクトの先頭であり、最後の「>;」までがオブジェクトの本体です。 推論エンジンオブジェクトの中には、記憶、推論、全推論の3つのメソッドがあります。

記憶メソッドは、「~ならば、~である。」の論理的な関係を記憶します。

推論メソッドは、論理的な関係を質問されたときに、その関係が正しいか三段論法で推論します。

全推論メソッドは、質問された論理関係に関するすべての関係を列挙します。

記憶メソッド

まず、記憶メソッドは、#line, #x, #yの引数を持ちます。#lineは、記憶させる論理関係を説明する文を設定します。 #x, #yは、#xならば#yであるというような、#x→#yの論理的な関係の引数を設定します。

<記憶 #line #x #y>にマッチするプログラムが呼び出されると、::self <addMethod <事実 #line #x #y>>が実行され ます。::selfというのは、自分自身のオブジェクトを示し、このプログラムの場合は、推論エンジンオブジェクト自身をあらわします。 <addMethodというのは、オブジェクトに引数の<事実 #line #x #y>の#line, #x, #y変数を展開したメソッドが登録されます。

例えば、以下のような推論エンジンオブジェクトの記憶メソッドの呼び出しを実行すると、


::<推論エンジン
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>;

        <推論 #x #x _ ()>;

        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;
>;

? ::推論エンジン <記憶 "aならば、bである。" a b>;
? ::推論エンジン <記憶 "bならば、cである。" b c>;

推論エンジン内に、<事実 "aならば、bである。" a b>、<事実 "bならば、cである。" b c>が新たに登録されます。 以下のように、推論エンジン内に登録されます。


<推論エンジン
        <事実 bならば、cである。 b c>
                ;
        <事実 aならば、bである。 a b>
                ;
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>
                ;

          …以下省略

新しく登録すると、順に上に登録されていきます。

推論メソッド

推論メソッドは、引数が3つのものと、4つのものの2種類が登録されています。 引数が3つのものが、外部からのI/Fとなるものです。4つのものが実際の推論処理を行います。引数が3つの推論メソッドから、引数が4つの推論メソッドを呼び出します。

引数が3つの推論メソッド


        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

#x→#yの関係が正しいか推論します。正しい場合は、その根拠が#pに設定されます。 このメソッドは、4引数の推論メソッドを第3引数に推論の開始点となる#xを含むリストを設定して呼び出します。

引数が4つの推論メソッド


        <推論 #x #x _ ()>;

        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

引数が4つあるメソッドは、2つの節で構成されます。呼び出されたときの引数のパターンマッチングにより、どちらが呼び出されるのかを選択します。

第1引数と第2引数が同じ場合には、最初の節にパターンマッチします。この状態は、推論の最終的な目的の状態で#x→#xの状態なので、これ以上の処理は行いません。

第1引数と第2引数が異なる場合には、2つ目の節にパターンマッチします。

まず、<事実 #a #x #x1>により、記憶メソッドによって記憶された事実の中で#xと一致するものを探します。#x→#yの推論をするために、#x→#x1→…→#yの関係になる途中の#x1を探し出しましょう。

次に、::sys <isUnregistered #x1 #L>により、新しく探し出した#x1が第3引数の#Lリストに含まれていないか判定します。含まれていれば、その#x1はすでに選ばれた選択子なので、ここでバックトラックが起り、もう一度上の処理で#x1の候補を探します。

含まれていなければ、4つの引数を持つ推論メソッドを#x1を起点として#x1→#yを推論するために<推論 #x1 #y (#x1:#L) #p>のように再帰的に呼びます。このとき第3引数にはすでに選択された#x1がリストに登録されます。再帰的な呼び出しによって途中の事実を順に探索していきます。

#x1と#yが等しくなる、つまり推論が完了すると最初の節が呼ばれて終了します。

さらに、再帰的な呼び出しの返るときに第4引数に順に推論に使われた論理の根拠が設定されていきます。

このあたり、言葉で説明すると複雑に見えるのですが、Prolog言語と同じパターンマッチとトラックバックを組み合わせたユニフィケーション処理が行われているだけです。

推論メソッドの実行例を示します。 以下のプログラムを実行します。


::<推論エンジン
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>;

        <推論 #x #x _ ()>;

        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;
>;



? ::推論エンジン <記憶 "aならば、bである。" a b>;
? ::推論エンジン <記憶 "bならば、cである。" b c>;

? ::推論エンジン <推論 a b #p>;
? ::推論エンジン <推論 a c #p>;

最後の2行が、推論メソッドによる、a→b, a→cが正しいかを問う質問となっています。

ファイルlogic2.carに保存し、デカルト言語の引数として実行すると以下のようになります。


$ descartes logic2.car
result --
        ::推論エンジン <記憶 aならば、bである。 a b>
-- true
result --
        ::推論エンジン <記憶 bならば、cである。 b c>
-- true
result --
        ::推論エンジン <推論 a b (aならば、bである。)>
-- true
result --
        ::推論エンジン <推論 a c (aならば、bである。 bならば、cである。)>
-- true

aならばbとaならばcが正しいと推論され、その根拠が第三引数にリストとして設定されています。

全推論メソッド

全推論メソッドは、引数の#xと#yが、#x→#yとなる関係を自動的に推論してすべて列挙します。


        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;


findall述語は、引数の述語をすべての組み合わせが終わるまで繰り返し実行します。 この場合、<推論 #x #y #p> <printlist #p>が実行され、推論とプリント処理が繰り返されてすべての組み合わせが推論されます。

全推論メソッドは、#xに変数が設定されて呼び出された場合には、#yに設定された値に至る論理関係がすべて推論してプリントされます。

#yに変数が設定されて呼び出された場合には、#xに設定された値から始まる論理関係がすべて推論されてプリントされます。

推論メソッドの実行例を示します。 以下のプログラムを実行します。


::<推論エンジン
        <記憶 #line #x #y>
                ::self <addMethod <事実 #line #x #y>>;

        <推論 #x #x _ ()>;

        <推論 #x #y #L (#a:#p)>
                <事実 #a #x #x1>
                ::sys <isUnregistered #x1 #L>
                <推論 #x1 #y (#x1:#L) #p>;

        <推論 #x #y #p>
                <推論 #x #y (#x) #p>;

        <全推論 #x #y>
                <findall <推論 #x #y #p> <printlist #p>>;
>;

? ::推論エンジン <記憶 "aならば、bである。" a b>;
? ::推論エンジン <記憶 "bならば、cである。" b c>;

? ::推論エンジン <全推論 a _>;
? ::推論エンジン <全推論 _ c>;

最後の2行が、全推論メソッドによる、a→?, ?→cの関係をすべて推論する質問となっています。 引数のアンダースコア"_"は、無名変数です。

ファイルlogic3.carに保存し、デカルト言語の引数として実行すると以下のようになります。


$ descartes logic3.car
result --
        ::推論エンジン <記憶 aならば、bである。 a b>
-- true
result --
        ::推論エンジン <記憶 bならば、cである。 b c>
-- true

 aならば、bである。
 aならば、bである。 bならば、cである。
result --
        ::推論エンジン <全推論 a Undef57>
-- true

 bならば、cである。
 aならば、bである。 bならば、cである。
result --
        ::推論エンジン <全推論 Undef122 c>
-- true

2. 会話

会話述語は、人間とのI/Fを受け付ける部分です。 まず、質問述語で、推論エンジンの推論メソッドまたは全推論メソッドに相当する処理かどうかを試します。

次に、三段論法述語で、推論エンジンの記憶メソッドに相当する処理かどうかを試します。

いずれでもない場合は、「わかりません」と回答します。


<会話 #res>
        (
                <質問 #p>       ::sys <is #res "はい">
                               <print "はい">
                               <printf "なぜならば、">
                               <printlist #p>
         |
                <質問>          ::sys <is #res "はい">
         |
                <三段論法>      <print "わかりました。">
                                ::sys <is #res "わかりました。">
         |
                                <print "わかりません。">
                                ::sys <is #res "わかりません。">
        )
        ;

::sys <is #res "はい">とあるのは、#res変数に"はい"を設定する組み込み処理です。 printは改行ありでプリントし、printfは改行なしでプリントします。 printlistは引数のリストを、外側の括弧をはすしてプリントします。

質問述語は、2つありますが、#p引数が付いているのが、推論メソッドを呼び出し、引数がないほうが全推論メソッドを呼び出します。

#p引数のある質問述語は、処理が成功すると「はい」「なぜならば」と回答し、推論根拠をプリントします。

引数のない質問述語は、処理が成功すると全推論メソッドが該当する解をすべて表示した後「はい」と答えます。

三段論法述語は、記憶メソッドにより論理関係を記憶し、「わかりました」と答えます。

"|"は、orを表します。 prolog言語と同じように"|"無しで、会話述語を並列して以下のように書くのと同じです。


<会話 #res>
         <質問 #p>       ::sys <is #res "はい">
                        <print "はい">
                        <printf "なぜならば、">
                        <printlist #p>
          ;

<会話 #res>
         <質問>          ::sys <is #res "はい">
         ;

<会話 #res>
         <三段論法>      <print "わかりました。">
                        ::sys <is #res "わかりました。">
         ;

<会話 #res>
         <print "わかりません。">
         ::sys <is #res "わかりません。">
        ;

"|"を使うほうが、簡潔に書けますね。

3. 質問機能

質問述語は、人間からの質問を構文解析し、推論エンジンを呼び出す部分です。できるだけ柔軟に質問を解析できるようにするため、複数の構文を受け付けるようにします。

質問述語には、合致する論理関係をすべて推論する推論エンジンの全推論メソッドを呼び出すものと、個別の論理関係を判定する推論エンジンの推論メソッドを呼び出すものの2種類があります。 質問述語のうち、引数がないものが全推論メソッドを呼び出すものです。 #p引数が一つあるものが、推論メソッドを呼び出すものです。

さらに全推論メソッドを呼び出す質問述語の中には、論理の開始点からすべての全推論をプリントアウトするものと、論理の帰結に至るまでの全推論の結果をすべてプリントアウトするものの2種類があります。


<質問>
        <* #x>
        ( "になるのは" | "であるには" | "であるのは" )
        ( "?" | "?" )          ::推論エンジン <全推論 _ #x>

        ;

<質問>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        ( "?" | "?" )          ::推論エンジン <全推論 #x _>
        ;


<質問 #p>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        (  "、"
         | ","
         | ","
        )
        <* #y>
        [ "であるか" | "ですか" | "か" ]
        ( "?" | "?" )           ::推論エンジン <推論 #x #y #p>
                                 ::sys <is #res "はい">
        ;

例えば、以下のような構文を受け付けます。


~になるのは?
~であるには?
~であるのは?

~ならば?
~だったら?
~であれば?
~は?

~ならば、~であるか?
~だったら、~ですか?
~は、~であるか?

論理の帰結までの全推論の構文解析

以下のような、構成になっています。


<質問>
        <* #x>
        ( "になるのは" | "であるには" | "であるのは" )
        ( "?" | "?" )          ::推論エンジン <全推論 _ #x>

        ;

まず、<* #x>は、ワイルドカード述語です。正規表現の*と同じく任意の文字列とマッチし、マッチした文字列を引数の変数に設定します。

次に( "になるのは" | "であるには" | "であるのは" )の中でどれかとマッチします。最後に、全角または半角の?とマッチすると推論エンジンオブジェクトの全推論メソッドを呼び出します。このとき、構文解析で*述語とマッチした文字列を全推論メソッドの後の引数に設定し、前の引数は変数で呼び出すと、論理の帰結を求めるような処理を行い合致する論理をプリントアウトします。

つまり、以下のような文章とパターンマッチし、~に対する論理の帰結を求める処理を行います。


~になるのは?
~であるには?
~であるのは?

論理の開始からの全推論の構文解析

以下のような、構成になっています。



<質問>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        ( "?" | "?" )          ::推論エンジン <全推論 #x _>
        ;


前項とほとんど同じですね。大きな違いは推論エンジンオブジェクトの全推論メソッドを呼び出すときに、第一引数に入力された文を設定し、第2引数を無名変数にしていることです。これにより、~ならば、...の組み合わせをすべて推論し列挙します。

以下のような日本語のメッセージに反応します。


~ならば?
~だったら?
~であれば?
~は?

個別の論理関係の判定の構文解析

この述語では、2つの変数#x, #yを、日本語の質問文から抽出します。 #x→#yであるか?の関係を質問するものです。

抽出された#x, #yは推論エンジンの推論メソッドに設定されて、論理的に正しいか判定します。

推論メソッドの#p引数には、推論が正しかったときの論理的な根拠が返されます。


<質問 #p>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        (  "、"
         | ","
         | ","
        )
        <* #y>
        [ "であるか" | "ですか" | "か" ]
        ( "?" | "?" )           ::推論エンジン <推論 #x #y #p>
                                 ::sys <is #res "はい">
        ;

以下のような日本語のメッセージに反応します。


~ならば、~であるか?
~だったら、~ですか?
~は、~か?
...

必ず文の中に、「、」と「?」が必要なことに注意してください。

4. 記憶のための三段論法

この述語では、2つの変数#x, #yを、日本語の質問文から抽出します。 #x→#yである関係を記憶するためのものです。

抽出された#x, #yは推論エンジンの記憶メソッドに設定されて、記憶されます。


<三段論法>
        <* #x>
        ("ならば" | "だったら" | "であれば" | "は")
        (  "、" | "," | "," )
        <* #y>
        ["である" | "です" | "だ"]
        ( "。" )                ::sys <concat #line (#x  "ならば、" #y "である。")>
                                ::推論エンジン <記憶 #line #x #y>
        ;

以下のような日本語のメッセージに反応します。


~ならば、~である。
~だったら、~です。
~であれば、~。
...

5. 入力と繰り返し

最後に以下の処理によって、何か文を1行入力します。その後、入力された行の内容を印刷して、会話述語を呼び出します。

そして、それらの処理を繰り返していきます。


? { ::sys <getline #l <print "# " #l> <会話 #x>> };

getline述語は、1行入力し、第一引数の変数に設定します。 そして、入力された行の内容を構文解析の入力として設定して、第2引数以降に書かれた処理を呼び出します。 上記処理では、printと会話の述語が呼び出されることになります。

会話述語の処理は、ここまでに述べてきたように入力された行の内容を構文解析し、記憶または推論をして答えを返します。

さらに、getline述語全体が、{}で括られていますね。{}は、内部の処理を0回以上繰り返すことを示します。内部の処理が失敗するとループするのを止めて終了します。

1行入力し、その内容をprintし、会話処理をし、成功すれば、どんどん繰り返して処理をするインタプリタのような処理をこの1行で実行しているのです。

6. 構造について

さて、今まで述べてきた、この例題の構造についてです。

大きく分けると、推論エンジンと会話処理に分けられることが分かると思います。


  会話処理   →   推論エンジン
 (構文解析) ←  (記憶と推論)

この例題では、推論エンジンに有向グラフの経路探索を使っています。 これは、入力された情報を、方向を持つグラフのパターンとして覚えます。 質問されたパターンが正しいか判定し、また、有効な経路を探索することを知識の推論として利用しています。基本的できわめてプリミティブな処理でありながら、結構、推論エンジンらしい動きをさせることができました。

この例題で最初に示してa,b,c,d,e,fの関係では以下のようなグラフになっていました。


   ↓←←←←←←←←←←←←←←←←
   ↓               ↑
   a → b → c → d → e 
                ↓
                 → f

このグラフ上で、aならばbなどの論理的な関係を推論していたのです。

次に、会話処理は、EBNF(拡張バッカス記法)による文脈自由文法を日本語の構文に適応したものです。 しかし、*述語については、文脈自由文法の領域をちょっと超えています。*の扱いが、デカルト言語では他の正規表現とは異なり動きが多少異なるのですが、ここでは割愛し、また別の機会に説明したいと思います。

この構造は、他にも応用が利くでしょう。

会話処理部分をより柔軟で、さまざまな入力に対応できるように、独立して改善していくことができます。 また、推論エンジンをもっと強力な論理的なパターンに対応できるようにしていくことができるでしょう。

それぞれの部分は、独立性が高いので、場面によって最適なモジュールに置き換えることも有効です。