А.4.4. Расширение набора правил — работа с составными высказываниями
Расширим теперь возможности программы таким образом, чтобы она могла работать с составными высказываниями. Это даст возможность охватить в ней не только вырожденный случай, рассмотренный в предыдущем разделе, но и более сложные. За основу возьмем следующую головоломку. Р4. Встречаются два персонажа, А и В, каждый из которых либо лжец, либо правдолюбец. Персонаж А говорит: "Мы оба лжецы". К какой категории следует отнести каждого из них? В этой задаче нам придется иметь дело с конъюнкцией, поскольку утверждение, высказанное персонажем А, моделируется выражением Эту конъюнкцию нужно разделить на выражения-компоненты и проанализировать их непротиворечивость. Очевидно, что А не может быть правдолюбцем, поскольку это противоречит утверждению, которое содержится в его реплике. Но программа должна самостоятельно "распаковать" эту конъюнкция для того, чтобы прийти к такому выводу. Нам также понадобится снабдить программу и средствами обработки дизъюнкции, поскольку, если предположить, что А лжет, нужно будет оперировать с отрицанием этого утверждения, которое преобразует выражение Т(А) v Т(B). Таким образом, в программу нужно включить правило выполнения отрицания составных высказываний и правило, которое "понимало" бы, что дизъюнкты вроде Т(А) в действительности являются предположениями. Составное выражение Т(А) v T(B) будем обрабатывать, предположив Т(А), и проанализируем, нет ли в нем противоречия. Если таковое не обнаружится, то можно предположить, что Т(А) v Т(B) совместимо с утверждением о том, что А лгун, т.е. F(A). Но если предположение Т(А) приведет к несовместимости, то нужно отказаться от него и предположить Т(Е). Если и это предположение приведет к несовместимости, то это означает, что утверждение Т(А) v T(B) несовместимо с предположением F(A). В противном случае Т(В) образует часть совместимой интерпретации исходного высказывания. В CLIPS составные высказывания проще всего представлять с помощью так называемой "польской" (или префиксной) нотации операций. Суть этого способа представления операций состоит в том, что символ операции предшествует символам операндов. Каждый оператор имеет фиксированное количество операндов, а потому всегда существует возможность однозначно установить область действия операции даже в случае, если операнды представляют собой вложенные выражения. Таким образом, выражение, представленное скобочной формой —(F(/4) ^ Т(В)), в польской записи будет иметь вид NOT AND F А Т В. Легче всего восстановить исходный вид выражения, представленного в польской нотации, просматривая его справа налево. При этом операнды считываются до тех пор, пока не встретится объединяющий их оператор. Полученное выражение оказывается операндом следующего оператора. В представленном выше выражении В является операндом одноместного оператора Т, а пара операндов Т(В) и F(A) объединяется оператором AND. Задавшись таким способом представления составных высказываний, сформируем правило выполнения отрицания дизъюнктивной и конъюнктивной форм, в котором будет использоваться функция flip, заменяющая "Т" на "F" и наоборот. (defrule not-or ?F <- (claim (content NOT OR?P?X?Q?Y)) => (modify?F (content AND (flip?P)?X (flip?Q)?Y)) (defrule not-and ?F <- (claim (content NOT AND?P?X?Q?Y)) => (modify?F (content OR (flip?P)?X (flip?Q)?Y))) Использование функции flip упрощает преобразование и позволяет перейти от выражения NOT AND F А Т В прямо к OR Т A F В, минуя OR NOT F A NOT Т В. Функция flip определена следующим образом: (def function flip (?P) (if (eq?P Т) then F else T)) Для упрощения мы ограничимся утверждениями в виде простых дизъюнкций или конъюнкций вида Т(А) v Т(В) или F(A) ^ T(B), но не будем использовать более сложные утверждения в форме F(B) ^ (T(А) v T) B)) или поскольку для решения большинства интересных головоломок вполне достаточно простых выражений. Наибольшие сложности при модификации нашей программы связаны с обработкой дизъюнктивных выражений, поскольку вывод о наличии противоречия может быть сделан только после завершения анализа всех членов операндов дизъюнкции. Например, нет противоречия между F(A) и Т(А) v F(B). Противоречие, которое обнаружится при обработке первого операнда дизъюнкции ДЛ) в предположении F(A), будет локальным в контексте Т(А). Но если мы вернемся к исходной дизъюнкции и попробуем проанализировать контекст F(B), то никакого противоречия обнаружено не будет, и, следовательно, интерпретация найдена. Реализовать такой анализ локальных и глобальных противоречий можно, добавив в шаблон объекта claim атрибут context: (def template claim (multifield content (type SYMBOL)) (multifield reason (type INTEGER) (default 0)) (field scope (type SYMBOL)) (field context (type INTEGER) (default 0))) Значение 0 в поле context означает, что мы имеем дело с глобальным контекстом, значение 1 — с локальным контекстом левого операнда, а значение 2 — с локальным контекстом правого операнда дизъюнкции. Пусть, например, анализируется дизъюнкция T(A) v F(B) причем Т(А) будет истинным в контексте 1, a F(B) — истинным в контексте 2. В этом случае все выражение будет истинным глобально, т.е. в контексте 0. Структуру объекта world также нужно модифицировать — внести в нее поле context. Это позволит отслеживать ход вычислений. Пусть, например, объект world имеет вид (world (tag 1) (scope truth) (context 2)). Это означает, что данный "мир" создан следующей парой предположений:
|