Языковые средства разработки интеллектуальных систем

advertisement
Министерство образования и науки Российской Федерации
Балтийский государственный технический университет «Военмех»
Кафедра информационных систем и компьютерных технологий
А.Н. ГУЩИН
ЯЗЫКОВЫЕ СРЕДСТВА РАЗРАБОТКИ
ИНТЕЛЛЕКТУАЛЬНЫХ СИСТЕМ
Учебное пособие
Санкт-Петербург
2006
2
Гущин, А.Н.
Языковые средства разработки интеллектуальных систем: учеб.
пособие / А.Н.Гущин; Балт. гос. техн. ун-т. – СПб., 2006. –
Учебное
пособие
содержит
основные
с.
сведения
о
языках
программирования PROLOG, LISP и CLIPS, применяемых для разработки
интеллектуальных систем. Имеются примеры применения синтаксических
конструкций рассматриваемых языков.
Предназначено для студентов и магистрантов, обучающихся по специальностям
«Автоматизированные
системы
обработки
«Информационные системы и технологии»
информации
и
управления»
и
3
ВВЕДЕНИЕ
Интеллектуальная (информационная) система – автоматическая или
автоматизированная система, использующая в своей работе элементы или
полностью построенная как система искусственного интеллекта. То есть,
использующая в своей работе моделирование определенных сторон
мыслительной деятельности человека или же решающая неформализованные
(и не формализуемые) задачи, не имеющего известного алгоритма для их
решения.
Для разработки интеллектуальных систем могут применятся различные
языковые средства и соответствующие им инструментальные средства
разработки.
Учитывая
многообразие
форм
построения
и
областей
применения современных интеллектуальных систем, в качестве языковых
средств
разработки
могут
применяться
как
традиционные
языки
программирования достаточно низкого уровня, такие как С, Pascal, Java или
Basic, так и специализированные языки разработки интеллектуальных
систем.
Языки
программирования
интеллектуальных
систем
–
языки
программирования, ориентированные на разработку интеллектуальных
информационных систем, как правило имеющие парадигму, отличную от
традиционных
для
языков
программирования
общего
назначения
процедурных парадигм.
Процедурные (императивные) парадигмы (процедурный подход) – как
сделать?
Декларативные парадигмы (декларативный подход) – что мы знаем?
что хотим узнать?
Промежуточное положение между процедурным и декларативным
подходом
занимает
объектно-ориентированный
подход
(ООП):
при
рассмотрении объектов в программе, как моделей объектов реального мира –
ООП можно отнести к декларативному подходу, при рассмотрении
4
реализации поведения объектов –
ООП
можно
отнести
к
императивному подходу.
К языкам программирования интеллектуальных систем можно отнести:
– LISP
– PROLOG
– CLIPS
– Haskell
– и многие другие
Как было отмечено выше, строго говоря, универсальные процедурные
языки
тоже
можно
отнести
к
языковым
средствам
разработки
интеллектуальных систем, точно также, как и языки специальных средств,
таких как оболочки экспертных систем и т.д.
Поскольку полностью охватить весь спектр языков разработки
интеллектуальных систем в рамках данного пособия не представляется
возможным, рассмотрим (на самом элементарном уровне) по одному
представителю трех семейств: из языков логического программирования –
ЯП PROLOG, из языков функционального программирования – ЯП LISP, из
специализированных языков разработки экспертных систем – язык системы
CLIPS.
Предлагаемое учебное пособие состоит из трех разделов. Первый
раздел посвящен рассмотрению принципов построения программ на ЯП
PROLOG. Во втором разделе приведены базовые сведения о ЯП LISP и его
использовании при разработки интеллектуальных систем. Третий раздел
освещает основные возможности ЯП CLIPS, применяемого в одноименной
среде разработки экспертных систем.
5
1. ЯЗЫК ПРОГРАММИРОВАНИЯ PROLOG
1.1. Основы языка Пролог
Язык
программирования
Пролог
(PROgramming
LOGic
–
программирование в терминах логики) предполагает получение решения
задачи при помощи логического вывода из ранее известных фактов и правил.
Пролог был создан в 1971 г. на факультете естественных наук в Марселе.
Программа на языке Пролог не является последовательностью
действий – она состоит из описания задачи (набор фактов и правил), а
Пролог-система сама строит логический вывод на основе введённых данных.
В
связи
с
этим
Пролог
считается
декларативным
языком
программирования.
Программа на прологе состоит из предложений. Предложения могут
быть трех видов: факты, правила, вопросы. Все предложения строятся из
термов. Терм – синтаксический объект одной из следующих категорий:
– константы;
– переменные;
– предикаты,
структуры
(составные
термы
или
функции),
состоящие из имени предиката и списка аргументов-термов; имена
предикатов начинаются со строчной буквы.
Константы
-
это
поименованные
конкретные
объекты
или
отношения. Константа начинается со строчной буквы, либо заключаются в
одинарные кавычки. Также константа может быть числом.
Переменные служат для обозначения объектов, значения которых
меняются в ходе выполнения программы. Имена переменных начинаются с
заглавных букв или знака «_» Область действия переменной – предложение.
Одноименные переменные в разных предложениях могут иметь разные
значения.
6
Специальным
знаком
«_»
обозначается
анонимная
переменная, которая используется тогда, когда конкретное значение
переменной не существенно для данного предложения. Значение анонимной
переменной не выводится на печать.
Предикат – это единый объект состоящий из совокупности других
объектов, называемых компонентами. Компоненты в свою очередь могут
быть также предикатами. Название предиката стоит перед скобками, а
компоненты внутри скобок перечисляются через запятую.
Название
предиката – функтор.
владелец (иван, книга(война и мир)).
функтор различается двумя параметрами: именем и числом параметров
point(X, Y, Z) и point(X, Y) – разные предикаты
point/3 – это означается, что у предиката point 3 аргумента,
point/2 – это означается, что у предиката point 2 аргумента.
Комментарии
Комментарии в программе помещаются между /* и */ (для любого
количества строк), либо после символа % на той же строчке.
Арифметические операции в языке PROLOG
В прологе выполняются следующие операции: +, -, *, /, mod, div.
Чтобы
арифметическое
выражение
рассчитывалось,
необходимо
использовать встроенный оператор is, который заставляет выполнять
арифметические операции.
Пример: X is 4+3*2.
Однако в среде Turbo Prolog оператор сопоставления is отсутствует,
вместо него сопоставление производится с помощью операции = (сравнение),
когда одним из операндов выступает неконкретизированная переменная, а
вторым – арифметическое выражение.
Операции сравнения. Для чисел: >, <, =>, <= Для всех: =, \=
1.2. Типы предложений в языке PROLOG
Простейшим типом предложения является факт.
7
Факт
–
это
безусловно
истинное
утверждение
Пролог-
программы. Факт записывается в виде предиката с точкой на конце.
Пример:
часть(двигатель, автомобиль).
земля_круглая.
parent(tom, bob).
Вторым типом предложений Пролога является вопрос или цель.
Цель – это средство формулировки задачи, которую должна решать
программа. Простой вопрос (цель) синтаксически является разновидностью
факта.
Пример: цель: мать (мария, юлия).
Имена написаны с маленькой буквы, т.к. это константы. В данном
случае программе задан вопрос, является ли мария матерью юлии. Если
необходимо задать вопрос, кто является матерью юлии, то цель будет иметь
следующий вид:
Цель: мать( X, юлия).
Сложные цели представляют собой конъюнкцию простых целей и
имеют следующий вид:
Цель: Q1, Q2,…,Qn, где запятая обозначает операцию конъюнкции, а Q1,
Q2,…,Qn – подцели главной цели.
Конъюнкция в Прологе истинна только при истинности всех
компонент, однако, в отличие от логики, в Прологе учитывается порядок
оценки истинности компонент (слева направо).
Пример:
Пусть задана семейная БД при помощи перечисления родительских
отношений в виде списка фактов:
мать(мария, анна).
мать(мария, юлия).
мать(анна, петр).
отец(иван, анна).
отец(иван, юлия).
8
Тогда вопрос, является ли
иван дедом петра по материнской
линии, можно задать в виде следующей цели:
Цель: отец(иван, X), мать(X, петр).
Третьим типом предложения является правило.
Правила – это предложения вида H :- P1, P2,…, Pn.
Символ «:-» читается как «если», предикат H называется заголовком
правила, а последовательность предикатов P1, P2,…, Pn называется телом
правила.
Предикаты P1, P2,…, Pn часто называют посылками. Заголовок следует
из тела правила. Заголовок истинен, если истинны все посылки в теле
правила.
Если в программе встречается несколько правил с одинаковым
заголовком, то это называется процедурой (правила в процедуре связаны по
«или»). Пример:
a:-b3, b4.
a:-b1, b2.
мать(мария, анна).
мать(мария, юлия).
мать(анна, петр).
отец(иван, анна).
отец(иван, юлия).
дед(X, Y):- отец(X, Z), мать(Z, Y).
дед(X, Y):- отец(X, Z), отец(Z, Y).
Тогда вопрос, является ли иван дедом петра, можно задать в виде
следующей цели:
Цель: дед(иван, петр).
Очень часто правила в Прологе являются рекурсивными. Например,
для нашей семейной БД предикат «предок» определяется рекурсивно:
предок(x, y): - мать(x, y).
предок(x, y): - отец(x, y).
предок(x, y): - мать(x, z), предок(z, y).
предок(x, y): - отец (x, z), предок(z, y).
9
В
среде
описанную
Turbo
структуру,
программа
Prolog
выражаемую
имеет
заголовками
более
разделов,
явно
обычно
следующих в следующем порядке:
domains /* определение типов данных */
global domains /* определение
глобальных типов данных */
database /* определение предикатов
динамической базы данных */
predicates /* определение
предикатов */
global predicates /* определение
глобальных предикатов */
clauses /* определение фактов
и правил */
goal /* определение цели */
Минимально необходимыми разделами являются predicates и clauses.
Раздел goal может находиться после или перед разделом clauses. Цель может
состоять из нескольких подцелей. Если программа разрабатывается для
работы в «пакетном» режиме, а не диалога с Пролог-системой, раздел goal не
может быть опущен.
1.3. Унификация переменных.
Унификация – процесс сравнения набора условий, содержащихся в
правиле, с заданными условиями, содержащимися в базе данных.
Установление
соответствия
между
термами
является
основной
операцией при вычислении цели. Она осуществляется следующим образом:
на
каждом
шаге
выбирается
очередной
терм
и
отыскивается
соответствующее выражение в БД. При этом переменные получают или
теряют значения. Этот процесс можно описать в терминах текстуальных
подстановок: «подставить терм t вместо переменной Y». Свободными
переменными в Прологе называются переменные, которым не были
присвоены значения, а все остальные переменные называются связанными
переменными.
Переменная
становится
связанной
только
во
время
10
унификации, переменная вновь
становится
свободной,
когда
унификация оказывается неуспешной или цель оказывается успешно
вычисленной. В Прологе присваивание значений переменным выполняется
внутренними
подпрограммами
унификации.
Переменные
становятся
свободными, как только для внутренних подпрограмм унификации отпадает
необходимость связывать некоторое значение с переменной для выполнения
доказательства подцели.
Правила унификации.
1. Если x и y-константы, то они унифицируемы, только если они
равны.
2. Если x- константа или структура, а Y-переменная, то они
унифицируемы, при этом Y принимает значение x .
3. Если x и y – предикаты, то они унифицируемы тогда и только тогда,
когда у них одинаковые имена (функторы) и набор аргументов и каждая пара
аргументов унифицируемы.
Пример:
Следующие предикаты унифицируемы
a(b, C, d(e, F, g(h, i, J))) и a(B, c, d(E, f, g(H, I, j)))
при этом B=b, C=c, E=e, F=f, H=h, I=I, J=j.
Пример:
Следующие предикаты унифицируемы
triangle(point(X, 5), point(X, 8),
point(5, Z))
triangle(point(2, 5), point(Y, 8),
point(5, A))
При этом X=2, Y=2, а переменные Z и A – неконкретизованы, но
становятся сцепленными. Если две переменные сцеплены, то при
конкретизации одной из них, второй переменной автоматически будет
присвоено то же самое конкретное значение, что и первой.
11
В прологе операция = кроме
сравнения
выполняет
сопоставление двух термов, с конкретизацией переменных. Если термы
согласуются, то результат истина.
Пример:
?- a(B,c(x,D))=a(d,c(X,3)).
Yes
B=d X=x D=3
Аналогично, /= дает истину, если термы не согласуются.
Пример:
?-a(x)/=a(d).
Yes
Предикат not (отрицание)
Предикат not (<утверждение>) успешен, если утверждение ложно.
Предикат fail (безуспешное выполнение)
Предикат fail всегда приводит к безуспешному выполнению правило, в
котором он использован, что приводит к поиску альтернативных решений.
При использовании внутренней цели поиск прекращается после
первого успешного сопоставления цели. Для получения всех возможных
решений используется предикат fail.
Пример:
cars('BMW').
cars('Toyota').
cars('Ford').
cars('Nissan').
go:-cars(X),write(X),nl,fail.
:-initialization(go).
/*предикат в скобках будет выполняться
автоматически при запуске программы */
Предикат true (истина)
Предикат true всегда выполняется успешно.
Пример:
a:-true. % это аналогично строчке a.
12
1.4. Списки
Список – это набор термов, следующих друг за другом. Пустой список
[ ]. Пример непустого списка: [a,b,c,d]. Список состоит из головы и хвоста,
где голова – это первый элемент списка, хвост – это список без первого
элемента. Операция деления списка на голову и хвост обозначается при
помощи вертикальной черты.
Пример:
?-[1,2,a,b]=[H|T].
H=1
T=[2,a,b]
?[]=[H|T].
No
В среде Turbo Prolog, имеющий явную типизацию, для использования
списков необходимо явно объявлять в разделе domains соответствующий
домен через домен элемента списка и символ *:
Пример:
domains
strlist=string*
В конкретных реализациях языка (конкретных средах разработки)
может присутствовать ряд встроенных предикатов для работы со списками.
Например, member(Elem, List), который успешен, если элемент Elem входит в
список List, append(List1, List2, List3) – успешен, если список List3 является
объединением первых двух списков и delete(L1,Elem,L2) – удаляет элемент
Elem из списка L1 и сопоставляет полученный список с L2.
Примеры использования:
?-member(3,[1,2,3]).
Yes.
?-member(X,[1,2,3]),X<2.
X=1
?-append([1,2],[2,3],L).
L=[1,2,2,3]
?-append(X,Y,[1,2,3]).
X=[]
13
Y=[1,2,3]
X=[1]
Y=[2,3]
X=[1,2]
Y=[3]
X=[]
Y=[1,2,3]
?-delete([1,2,3],2,L).
L=[1,3]
При отсутствии встроенных предикатов для работы со списками, они
могут быть легко реализованы самостоятельно, например, как показано
ниже:
member(Name, [Name|_]).
member(Name, [_|Tail]):member(Name, Tail).
При необходимости, легко могут быть реализованы и другие операции
обработки списков:
length([], 0). /* предикат
для нахождения количества
элементов в списке */
length([X|L], N):length(L, M), N is M+1.
Следует отметить, что так определенный предикат length, если при
обращении к нему вторым аргументом используется переменная, уже
связанная с некоторым целым числом, будет успешно доказан, только если
оно соответствует длине списка, переданного первым аргументом.
write_list([])./* предикат
для вывода элементов списка
на экран */
write_list([H|T]):write(H),nl,write_list(T).
read_list([X|T]):write('Введите элемент: '),
read(X), X\=end,!,
read_list(T). %ввод списка
14
1.5. Откат (backtrace)
Откат – это поиск с возвратом, в результате которого находятся все
варианты решения.
Пример:
большой(медведь).
большой(слон).
маленький(кот).
бурый(медведь).
черный(кот).
серый(слон).
темный(Z):-черный(Z).
темный(Z):-бурый(Z).
?-темный(X),большой(X).
При непосредственном обращении к Пролог-системе (в командной
строке интерпретатора) производится поиск всех возможных решеений с
использованием механизма отката до точки (предиката), для которого ещё
остались ранее не рассмотренные решения.
1.6. Предикат ! (отсечение)
Отсечение – встроенный предикат, который заставляет Прологсистему
забыть все указатели отката, установленные во время попыток
вычислить текущую подцель (отмена поиска альтернатив для текущей
подцели).
Всегда
выполняется
успешно.
Введение
«!»
повышает
эффективность программы, сокращая время перебора и объем памяти.
Примеры:
Программа без отсечения:
data(one).
data(two).
data(three).
test(X,Y):-data(X),data(Y).
test('four','five').
?-test(X,Y).
X=one
Y=one
15
X=one
Y=two
X=one
Y=three
X=two
Y=one
X=two
Y=two
X=two
Y=three
X=three
Y=one
X=three
Y=two
X=three
Y=three
X=four
Y=five
Эта же программа, но с отсечением:
data(one).
data(two).
data(three).
test(X,Y):-data(X),!,data(Y).
test('four','five').
?-test(X,Y).
X=one
Y=one
X=one
Y=two
X=one
Y=three
Другие примеры использования предиката отсечения:
f(X, 0):-X<3, !
f(X, 2):- 3=<X, X<6, !.
f(X, 4):-6 =<X, !.
minimum(X, Y, X):- X<=Y, !.
minimum(X, Y, Y):- Y<X.
add(X, L, L):-member(X, L), !.
16
add(X, L, [X|L]).
factorial(0, 1):-!. /* предикат
для вычисления факториала
factorial(N, RES) :- N1 is N-1,
factorial(N1, N1fak),
Res is N*N1fak.
1.7. Представление данных структурами.
С помощью структур (составных термов) могут представляться
сложные
структуры
данных,
аналогичные,
например,
«записям»
процедурных языков программирования.
Пример (для среды Turbo Prolog):
domains
i=integer
s=symbol
d=dt(i, s, I) /* дата:
число, месяц, год)
predicates
db(s, d) /* имя и дата рождения */
clauses
db(“Mike”, dt(01, “Januar”, 1980))
1.8. Обновление базы данных Пролога.
Для изменения утверждений в базе данных в Прологе имеется
следующий набор встроенных утверждений.
assertz(X)
X – конкретизированный терм, не являющийся переменной. Терм
интерпретируется как утверждение и добавляется в базу данных Пролога.
Утверждение помещается в конец базы данных, или добавляется к
существующей процедуре с тем же функтором и арностью. (В некоторых
реализациях Пролога возможны исключения от этого правила, поэтом
необходимо обратиться к к руководству по конкретной системе. Например, в
17
среде
Turbo
Prolog
возможно
изменение только утверждений,
описанных как динамическая база данных в разделе database, причем они
должны быть только фактами.)
asserta(X)
Определяется аналогично предикату assertz(X), но новое утверждение
становиться первым в базе данных или первым в процедуре с теми же
функтором и арностью.
retract(X)
X –конкретезированный составной терм. Из базы данных удаляется
первое утверждение, голова и хвост которого сопоставляются с термом X.
retractall(X)
Удаляет из базы данных все утверждения, функтор и арность которых
сопоставимы с X.
abolish(P, A)
Удаляет из базы данных все утверждения для процедуры P с арностью
A.
consult(F)
Целевое утверждение consult(F) добавляет утверждения из файла F в
конец существующей базы данных. Его нельзя передоказать. Если файл F не
может быть открыт – выдается сообщение об ошибке. Пролог пытается
сопоставить цель с вновь добавленным утверждением только в том случае,
если при прямом доказательстве попытка согласования сущекствующего
утверждения закончилась неудачей.
reconsult(F)
Аналогичен предикату consult, но утверждения, считываемые в базу
данных, замещают ранее записанные утверждения с теми же функтором и
арностью.
save(F)
Записывает в файл текущее состояние базы данных для последующего
восстановления предикатами consult и reconsult.
18
Более подробные сведения о
языке программирования Пролог
(PROLOG), особенностях его реализации и применения при построении
интеллектуальных систем различного масштаба можно найти в специальной
литературе [1, 2, 3].
2. ЯЗЫК ПРОГРАММИРОВАНИЯ LISP
2.1. История создания ЯП LISP. Парадигма функционального
программирования.
LISP (LISt Processing – обработка списков) – это один из старейших
среди существующих языков программирования. LISP – является одним из
языков, соответствующих парадигме функционального программирования.
Фундаментальное отличие функционального программирования от так
называемого процедурного, или императивного заключается в следующем.
Императивная
программа
представляет
собой
набор
инструкций,
последовательно выполняя которые, исполнитель (компьютер) сможет
прийти к определенной цели, причем исполнитель наделен возможностью
хранения промежуточных результатов. При данной схеме программирование
сводится к изобретению того, как данная задача может быть решена на
компьютере. Возникает одна основная проблема при написании кода –
программист уделяет слишком большое внимание особенностям реализации
задачи в данных условиях (например, на архитектуре i386) в ущерб
собственно
самой
программированию
задаче.
Принципиально
является
так
иным
называемое
подходом
к
функциональное
программирование.
Основная
идея
его
такова
программы
строятся
из
«чистых»
математических функций, каждая из которых свободна от побочного
эффекта, то есть не в состоянии изменить среду вычислений. Рассмотрим
следующий достаточно абстрактный пример. Пусть у нас есть множество.
Задав над этим множеством отображение, мы получим его множество
19
значений. Так вот, естественно
представить
получившееся
множество значений в виде совокупности отображения и исходного
множества. Таким образом, любое множество представимо в виде
совокупности исходного множества и композиции отображений. Причем, что
важно, нас не интересуют «промежуточные» результаты действия какой-либо
подпоследовательности отображений на исходное множество — они заданы
той самой композицией. На каждом шаге в действительности производится
одна и та же операция, только в качестве исходного множества берется
результат действия предыдущих отображений на самое первое множество,
что естественно приводит нас к рекурсии. Подобная идея очень хорошо
подходит для решения задач, в которых основную роль играют исходные
данные, и задача состоит в замене формы представления данных на какуюлибо иную. В эту сферу попадает программирование экспертных систем,
логическое программирование, символьные вычисления и т.д.
Существует очень большое количество функциональных языков
программирования, но наиболее известным из них, безусловно, является Lisp.
Lisp был реализован Джоном Мак-Карти, сотрудником лаборатории
искусственного интеллекта Стэнфордского университета, в 1958 году.
2.2. Основные особенности ЯП LISP
К числу основных особенностей языка Лисп относится то, что
программой является несколько определенных пользователем функций. С
точки зрения синтаксиса лисп-функция, как и обрабатываемые ею данные,
представляет собой так называемое S-выражение. В простейшем случае Sвыражением является атом (идентификатор или число), в более сложном список, т.е. последовательность элементов, разделенных обычно пробелом и
заключенных в круглые скобки. Лисповские списки имеют рекурсивную
структуру: элементом списка может быть произвольное S-выражение - как
атом, так и список , например: (1() (a b (c)) class).
20
Некоторые
S-выражения
можно
вычислять,
получая
в
результате значения (тоже S-выражения); такие выражения называются
формами. Формой может быть переменная, т.е. атом-идентификатор,
которому было присвоено значение одной из лисповских функций
(значением такой формы является текущее значение переменной). Формой
является также список-обращение к функции вида (f a1 a2 ... an), где f – имя
функции, а ai – её аргументы (n≥0). Программа на Лиспе представляет собой
последовательность таких форм, и ее выполнение заключается в их
вычислении.
В большинстве реализаций (версий) языка Лисп имеется много
встроенных (стандартных) функций, на основе которых составляется
программа. Ниже кратко описаны некоторые из них.
2.3. Символы и их значения
Для представления объектов окружающего мира мы используем их
названия, то есть некоторые символы, ссылающиеся на реальные объекты.
Значением символа можно назвать тот самый объект, который мы описываем
символом. Очень важно заметить, что значением является именно объект, а
не еще один символ для его описания.
Лисп оперирует символами (или именами переменных в привычном
представлении, что не совсем верно), причем каждый из символов имеет
значение. Таким образом, каждый из символов Лиспа – это отображение из
пространства
объектов
в
пространство
имен,
которым
можно
воспользоваться в обратную сторону, то есть «вычислить» символ.
Программирование на языке Лисп предполагает построение некоторой
модели предметной области, сущностями которой оперирует программа. Но
все же «изнутри» они представлены обычными структурами данных, поэтому
мы можем говорить о структурах данных, представляемых символами.
21
Пусть помощью функции set
символ а стал обозначать число 4.
Если мы далее подадим на ввод Лиспа символ а, его значением в любом
контексте будет 4.
Символ, значением которого является всегда он сам, называется
атомом. Примерами атомов могут служить Т и NIL (логическая «истина» и
логическая «ложь»), а также число 4 (как и любое другое число). Для того
чтобы воспользоваться самим символом а (как реальным объектом),
существует возможность блокирования вычисления символа с помощью
функции quote.
2.4. Структуры данных
Основной и всегда используемой структурой данных Lisp является
список. Список строится перечислением символов через пробел и
заключением получившегося выражения в скобки, например, (А В С).
Поскольку сам список, также является символом, то он может являться
элементом других списков – (А (В С (D(Е F)))). В этом случае список должен
представлять собой правильно построенное скобочное выражение, в котором
каждой открывающей скобке соответствует закрывающая. Атомы и списки
(то есть то, что раньше мы называли символами) называются еще и
символьными выражениями, или s-выражениями (см. 2.2).
Внутреннее представление списка в Lisp классическое, то есть список
состоит из набора связанных так называемых списочных ячеек – структуры
из двух элементов значения и указателя на следующую ячейку. Последняя
ячейка ссылается на пустой список (), то есть значением поля указателя
является NIL. Следует отметить, что в Лиспе понятия пустого списка и
булевского значения «ложь» совпадают, то есть () эквивалентно NIL.
2.5. Функции. Запись функций и программ
Как и всякое символьное выражение, список должен иметь значение.
Функции в языке Лисп записываются в виде списков. Значением списка
является результат вызова функции с именем, представляемым в виде
22
первого
элемента
списка,
и
остальными элементами списка в
качестве аргументов.
Продемонстрируем сказанное (в дальнейшем левая часть строки – это
то, что подается на ввод интерпретатора Lisp, а правая часть, после знака
=>,—то, что было выдано интерпретатором):
(+ 1 2) => 3
Теперь можно продемонстрировать упомянутые выше set и quote:
(quote a) => а
Лисп-функции могут как вычислять, так и не вычислять свои
аргументы прежде чем использовать их. Как правило, функции вычисляют
аргументы и используют для работы вычисленные значения. В частности, так
работает функция set.
(set (quote a) 1) => 1
а => 1
Значение атома 1 (то есть 1) было присвоено значению списка (quote
а), то есть результату вызова функции quote, которым, в свою очередь,
являлось а.
Приведем еще один пример:
(set (quote а) 1) => 1
(set (quote b) 2) => 2
(set b а) =>
Wrong type argument: symbolp, 2
В последнем вызове интерпретатор попытался присвоить значение
символа а (то есть 1) значению символа b (то есть 2), которое, в свою
очередь, является атомом. Поскольку вызов quote применяется очень часто,
существует его сокращенная запись в виде ’а, что означает (quote а), и
поскольку вызов (set (quote также очень част, то его сокращенная запись
выглядит как (setq. При выполнении любой из вышеперечисленных операций
возвращается новый символ, то есть физическая структура списков не
меняется. Теоретически любая чисто функциональная программа не должна
изменять свое окружение, а вместо этого возвращает новое значение которое
в свою очередь, будет аргументом следующей функции. На практике же
23
иногда
приходится
(либо
для
ускорения
работы,
либо
для
упрощения кода) пользоваться так называемыми разрушающими функциями
которые изменяют физическую структуру заданного списка. Примерами
таких функций могут служить rplaca и rplacd заменяющие поле значения и
поле
указателя
ячейки
соответственно,
а
также
setf
выполняющая
«обобщенное» присваивание то есть заносящая значение непосредственно в
ячейку памяти занимаемую символом.
Функциональная программа на языке LISP может быть записана в виде
одной функции (на самом деле, являющейся композицией многих),
принимающей исходные данные и возвращающей целевые. Запись этой
функции происходит с помощью списков так как показано выше, а список, в
свою очередь является формой представления данных, которыми можно
манипулировать, и в результате, мы получаем, что программы и данные
представляются в Лиспе одинаково. Это дает возможность развития так
называемого
программирования
управляемого
данными
(data
driven
programming), при котором поступающие на вход данные обрабатываются
путем интерпретации их в качестве элементов кода программы. Еще одной из
возможностей Lisp, позволяющей реализовать подобное программирование,
является возможность обращения к интерпретатору для любого вычислимого
выражения внутри программы с помощью вызова функции eval:
(eval ’(+ 1 2)) => 3
2.6. Функции с побочным эффектом.
Функции в языке ЛИСП могут обладать не только возвращаемым
значением, но и побочным эффектом (side-effect), который заключается в
некотором изменении среды вычислений. По аналогия с императивным
программированием можно сказать, что функции, в которых нас интересует
возвращаемое значение, – это «настоящие» («чистые») функции, а если нас
интересует побочный эффект – это процедуры.
Примером подобной функции с основным результатом в виде
побочного эффекта может служить set, которая на самом деле присваивает
24
символу значение, а возвращает
присвоенное значение. Несмотря
на то, что с помощью «чистых» функций, не изменяющих окружения, можно
построить
все
действительно
функциональное
больших
программ
программирование,
приходится
при
написании
жертвовать
чистотой
идеологии ради комфорта скорости и расширяемости работы.
2.7. Базовые функции
В Лиспе существует минимальный набор функций с помощью
которых, вообще говоря, может быть построена любая программа (то есть
более сложная функция). Рассмотрим их на примерах.
Операции над списками
(car l)
Значением аргумента l должен быть непустой список, тогда значением
функции является первый элемент (верхнего уровня) этого списка.
(cdr l)
Значением аргумента l должен быть непустой список, и значением
функции
является
"хвост"
этого
списка,
т.е.
список,
полученный
отбрасыванием первого элемента.
(car ’(a b с)) => a
(cdr ’(a b c)) => (b c)
(cdr ’(a)) => nil
Последний пример демонстрирует то, что последняя ячейка указывает
на nil.
Кроме этих двух функций-селекторов элементов списка часто
используются функции, являющиеся их суперпозициями. Имена всех таких
функций начинаются на букву c, а заканчиваются на букву r, между ними же
может стоять произвольная комбинация из не более чем 5 букв a и d,
например,
(cadar l)≡(car(cdr(car l))) .
25
Предполагается, что список-
аргумент l всех этих функций, так
же как и следующей функции nth, содержит необходимое число элементов (в
противном случае вычисления прерываются).
(car (cdr ’(a b c))) => b
(cadr ’(a b c)) => b ;; car от cdr
Функции доступа к элементам списка:
(nth n l)
Значением аргумента n должно быть положительное целое число
(обозначим его N), а значением аргумента l - список. Значением функции
является N-й от начала элемент этого списка.
(last l)
Функция выбирает последний (от начала) элемент списка, являющегося
значением ее аргумента.
(cons e l)
В
отличие
от
предыдущих
функций
эта
функция
является
конструктором, т.е. строит новый список, который и выдает в качестве своего
результата. Первым элементом этого списка будет значение аргумента e, а
хвостом списка - значение аргумента l .
(cons ’a ’(b с)) => (a b c)
(cons ’a ( )) => (a)
(cons (car ’(a b c)) (cdr ’(a b c)))
=> (a b c)
(append l1 l2)
Эта функция осуществляет слияние (конкатенацию) двух списков,
являющихся значением двух ее аргументов.
(list e1 e2 ... en )
Еще одна функция конструктор, она имеет произвольное количество
аргументов, из их значений она строит список (количество элементов
результирующего списка равно количеству аргументов).
Арифметические функции
(add1 n)
26
Значением аргумента этой
функции
должно
быть
число,
функция прибавляет к этому числу 1 и выдает результат в качестве своего
значения.
(sub1 n)
Значением аргумента должно быть число, функция вычитает из него 1
и выдает результат в качестве своего значения.
(+ n1 n2)
Значениями обоих аргументов функции должны быть числа, результат
вычисления функции - их сумма.
(- n1 n2)
Значениями аргументов должны быть числа, значение функции - их
разность.
(length l)
Значением аргумента l должен быть список, значением функции
является количество элементов (верхнего уровня) этого списка, например:
(mod n1 n2)
Значениями обоих аргументов функции должны быть целые числа.
Функция выполняет деление нацело первого числа на второе, и результат
выдает в качестве своего значения.
(rem n1 n2)
Значениями аргументов функции должны быть целые числа, результат
вычисления функции - остаток от деления первого числа на второе.
2.8. Предикаты
Предикатом обычно называется форма (выражение), значение которой
рассматривается
как
логическое
значение
"истина"
или
"ложь".
Особенностью языка Лисп является то, что "ложью" считается пустой
список, записываемый как () или nil, а "истиной" - любое другое выражение
(часто в этой роли выступает атом T).
(null e)
27
Эта
функция
проверяет,
является ли значение ее аргумента
пустым списком: если да, то значение функции равно T, иначе – ().
(atom х)
Предикат atom возвращает истинное значение, если его аргумент
является атомом, и ложь – в противном случае:
(atom ’х) => Т
(atom ’(a b c)) => NIL
(atom nil) => T
(atom ’( )) => T ;; Пустой список –
;; это nil
(atom ’(nil)) => NIL
;; (nil) - одноэлементный список
(atom (atom (+ 2 1))) => Т ;; Т - атом
Предикаты (функции) сравнения
(eq e1 e2)
Функция сравнивает значения своих аргументов, которые должны быть
атомами-идентификаторами. В случае их
совпадения
(идентичности)
значение функции равно T, иначе – ().
(eql e1 e2)
В отличие от предыдущей функции eql сравнивает значения своих
аргументов, которыми могут быть не только атомы-идентификаторы, но и
атомы-числа. Если они равны, то значение функции равно T, иначе - ().
(equal e1 e2)
Функция производит сравнение двух произвольных S-выражений значений своих аргументов. Если они равны (имеют одинаковую структуру и
состоят из одинаковых атомов), то значение функции равно T, иначе - ().
(neq e1 e2)
Аналог, но значения аргументов сравниваются на "не равно".
(member a l)
Функция производит поиск атома, являющегося значением первого ее
аргумента, в списке (на верхнем его уровне), являющемся вторым
аргументом. В случае успеха поиска значение функции равно T, иначе - ().
(gt n1 n2) или (> n1 n2)
28
Значениями аргументов этой
функции
должны быть числа.
Если первое из них больше второго, то значение функции равно T, иначе – ().
(lt n1 n2) или (< n1 n2)
Аналог, но числа сравниваются на "меньше".
Логические функции
Так называются три функции, реализующие основные логические
операции.
(not e)
Эта функция, реализующая "отрицание", является дубликатом функции
null: если значение аргумента равно () ("ложь"), то функция выдает результат
T ("истина"), а при любом другом значении аргумента выдает результат ().
(and e1 e2 ... ek) (k≥1)
Это "конъюнкция". Функция по очереди вычисляет свои аргументы.
Если значение очередного из них равно () ("ложь"), то функция, не вычисляя
оставшиеся аргументы, заканчивает свою работу со значением (), а иначе
переходит к вычислению следующего аргумента. Если функция дошла до
вычисления последнего аргумента, то с его значением она и заканчивает
свою работу.
(or e1 e2 ... ek) (k≥1)
Это "дизъюнкция". Функция по очереди вычисляет свои аргументы.
Если значение очередного из них не равно () ("ложь"), то функция, не
вычисляя оставшиеся аргументы, заканчивает свою работу со значением
этого аргумента, в противном случае она переходит к вычислению
следующего аргумента. Если функция дошла до вычисления последнего
аргумента, то с его значением она и заканчивает свою работу.
К числу логических функций можно отнести и лисповское условное
выражение:
[cond (p1 e1,1 e1,2 ... e1,k1) ...
(pn en,1 en,2 ... en,kn)]
где (n≥1, ki≥1)
29
Функция
последовательно
cond
вычисляет
первые элементы своих аргументов – обращения к предикатам pi. Если все
они имеют значение () ("ложь"), тогда функция заканчивает свою работу с
этим же значением. Но если был обнаружен предикат pi, значение которого
отлично от (), т.е. он имеет значение "истина", тогда функция cond уже не
будет рассматривать остальные предикаты, а последовательно вычислит
формы ei,j из этого i-го аргумента и со значением последнего из них закончит
свою работу. Заметим, что поскольку значения предыдущих форм из этого
аргумента нигде не запоминаются, то в качестве этих форм имеет смысл
использовать только такие, которые имеют побочный эффект, например,
обращение к функции присвоения значения.
2.9. Определения функций. Формализм их задания
Механизм объявления функций заимствован из лямбда-исчисления
Черча, то есть основан на методе представления «безымянных функций» с
помощью вычислимых выражений. «Безымянная» функция (или лямбдавыражение)
представляет
собой
совокупность
набора
формальных
переменных и действия с ними. Пример лямбда-выражения:
(lambda (х у) (+ х у))
Само по себе лямбда-выражение не воспринимается интерпретатором,
а вот лямбда-выражение, примененное к фактическим аргументам, уже
вычислимо:
((lambda (х у) (+ х у)) 1 2) => 3
При
вызове
лямбда-выражения
фактические
параметры
по
определенным правилам связываются с формальными и над ними
выполняется заданное действие.
Таким образом, каждый раз, когда нам нужна функция, мы можем
записать необходимое лямбда-выражение, примененное к фактическим
параметрам. Ясно, что это весьма неудобно (хотя и правильно с «идейной»
точки зрения), поэтому Lisp предоставляет возможность именования
30
конкретных
лямбда-выражений
для их последующего повторного
использования.
Для этого служит встроенная функция defun, к которой возможны
следующие (равноценные) обращения:
(defun f (lambda (v1 v2 ... vn) e))
или
(defun f (v1 v2 ... vn) e).
Вычисление функции defun в качестве побочного эффекта приводит к
появлению в программе новой функции с именем f ; vi – формальные
параметры новой функции (n≥0), а e - форма, зависящая от vi. Заметим, что
таким образом определяется обычная лисп-функция, т.е. функция с
фиксированным количеством аргументов, которые всегда вычисляются при
обращении к ней.
При последующем обращении к этой уже определенной функции
(f a1 a2 ... an)
сначала вычисляются аргументы (фактические параметры) ai, затем
вводятся локальные переменные vi, которым присваиваются значения
соответствующих аргументов ai, и далее вычисляется форма e при этих
значениях переменных vi, после чего эти переменные уничтожаются.
Значением данной формы становится значение функции f при аргументах ai.
Лексические переменные (формальные параметры функций) связаны
лишь в пределах той формы, в которой они определены, то есть изменение их
значений не влияет на значения одноименных внешних переменных.
В силу рекурсивной природы списков как таковых для определения
функций можно использовать рекурсию, хотя возможности Лиспа не
исключают и обычные, императивные определения. В качестве примера
можно привести следующее рекурсивное определение функции вычисления
факториала:
(defun myfactor (х)
(cond((equal х 1) 1)
(t (* x (myfactor (- x 1))))))
31
Фундаментальное значение
рекурсии состоит в ее применении
всюду, где требуются циклические вычисления, поскольку «чистая»
функциональная программа не содержит операторов цикла.
2.10. Функционалы
Кроме обычных функций в Lisp существует также возможность
объявления
функций
более
высокого
порядка,
или
функционалов.
Функционал – это функция, одним из аргументов которой, в свою очередь,
является функция. В различных случаях функциональный аргумент может
использоваться и как данные (в том случае, если он не влияет на
вычисления), и как обычная функция (в том случае, если он используется как
средство, определяющее вычисления):
(car ’(lambda (x) (list x))) => lambda
;; CAR - функция,
;; лямбда-выражение - данные;
((lambda (x) (list x)) ’car) => (car)
;; CAR - данные,
;; лямбда-выражение – функция
Функционал – это функция, в которой функциональный аргумент
используется в позиции и в роли функции.
Пример пояснит ситуацию. Часто используются так называемые
отображающие функционалы МАРx (где x—CAR или CDR), применяющие
функциональные аргументы последовательно к CAR или CDR списка и
составляющие список результатов:
(mapcar ’(lambda (x) (print x))
’(1 2 3))
=> 1\n2\n3\n
(mapcdr ’(lambda (x) (print
(car x))) ’(1 2 3))
=> 2\n3\n nil\n
32
2.11. Свойства символов
В Lisp каждый символ имеет так называемые свойства. Удобно
представлять
символы
в
виде
структуры
из
нескольких
значений:
непосредственно самого значения символа, а также некоторых присвоенных
ему именованных значений, называемых свойствами. На самом деле свойства
представляются списком, хранящимся вместе с символом (список свойств,
property list, proplist), следующего вида: (имя1 значение1 имя2 значение2. . . ),
например, у символа «кошка» может быть такой список свойств:
(шерсть густая цвет-шерсти белый
цвет-глаз голубой)
В Common Lisp (одна из стандартизованных реализаций языка Лисп)
получение определенного свойства символа выполняется с помощью
функции get:
(get ’кошка ’цвет-шерсти) => белый
Присваивание же выполняется с помощью объединения обобщенного
присваивания setf и get. Причем подобным образом может быть и заведено
новое свойство:
(setf (get ’кошка ’природная-еда) ’мыши)
=> мыши
(get ’кошка ’природная-еда) => мыши
Полный список пользовательских свойств может быть получен с
помощьюфункции symbolplist:
(symbol-plist ’кошка)
=> (природная-еда мыши)
Каждый символ в Lisp помимо определенных пользователем свойств
содержит также список системных свойств, таких, как, например, лямбдавыражение (если с данным символом связано определение функции), тип
символа и т. д. Эти свойства изменяются с помощью описанных выше
функций set, defun и пр.
Таким образом, является ли данный символ функцией или нет,
принадлежит ли он тому или иному типу и т. п., является свойством самого
33
символа,
что
позволяет
легко
манипулировать
этими
свойствами (попробуйте в С убрать у main() свойство быть функцией).
Эта возможность Lisp позволяет заметно облегчить написание
программ, управляемых данными, семантических сетей, а также объектноориентированных программ.
2.12. Основные недостатки языка Лисп
Самым существенным недостатком стоит считать огромное число
различных диалектов Lisp. На сегодняшний день существует около 90
различных (совместимых, безусловно, на программах, использующих
«чистый» Lisp, но иногда отличающихся поведением даже в рамках и этих
функций) вариантов Lisp, включая достаточно отдельные, но все же
происшедшие из него языки, такие как Scheme. Наиболее известными можно
считать диалекты MacLisp, FranzLisp и ZetaLisp.
Определенные надежды внушает принятый в 1984 году стандарт
Common Lisp, разработанный Гаем Стилом. Теперь можно писать на
Common Lisp, не слишком заботясь о совместимости с иными диалектами, –
но все же следует быть очень осторожным, поскольку даже интерпретаторы
Common Lisp не всегда ведут себя одинаково в тех или иных ситуациях.
Более подробные сведения о языке программирования LISP и
особенностях его конкретных реализаций можно найти в специальной
литературе [3, 4, 5, 6].
34
3. СРЕДА CLIPS
3.1. Общие сведения и краткая история среды CLIPS
Название CLIPS – аббревиатура от C Language Integrated Production
System. Язык был разработан в центре космических исследований NASA
(NASA’s Johnson Space Center). Первая версия системы вышла в 1984 году,
текущая версия 6.1. В CLIPS используется оригинальный LISP-подобный
язык программирования, ориентированный на разработку ЭС. Использование
C в качестве языка реализации объясняется тем, что компилятор LISP не
поддерживается частью распространенных платформ, а также сложностью
интеграции LISP-кода в приложения, которые используют отличный от LIPS
язык программирования. Разработанная ими система в настоящее время
доступна во всем мире, и нужно сказать, что по своим возможностям она не
уступает множеству гораздо более дорогих коммерческих продуктов.
CLIPS является одним из распространенных инструментальных
средств разработки экспертных систем (ЭС). Представляя собой логически
полную среду, содержащую встроенный редактор и средства отладки, CLIPS
является оболочкой ЭС. Первая версия представляла собой, по сути,
интерпретатор порождающих правил. Процедурный язык и объективноориентированное расширение CLIPS Object-Oriented Language (COOL) были
включены
в
этот
программный
продукт
только
в
1990-х
годах.
Существующая в настоящее время версия может эксплуатироваться на
платформах UNIX, DOS, Windows и Macintosh. Она является хорошо
документированным общедоступным программным продуктом и доступна в
интернете с множества университетских ftp-серверов. Исходный код
программного пакета CLIPS распространяется совершенно свободно и его
можно установить на любой платформе, поддерживающей стандартный
компилятор языка C. Однако рекомендуется пользоваться официальной
версией для определенной платформы, поскольку такие версии имеют
35
пользовательский
интерфейс,
включающий
меню
команд
и
встроенный редактор.
CLIPS использует продукционную модель представления знаний и
включает в себя язык представления порождающих правил и язык описания
процедур.
Основными компонентами языка описания правил являются база
фактов (fact base) и база правил (rule base). На них возлагаются следующие
функции:
– база фактов представляет собой исходное состояние проблемы;
– база правил содержит операторы, которые преобразуют состояние
проблемы, приводя его к решению.
Машина логического вывода CLIPS сопоставляет эти факты и правила
и выясняет, какие из правил можно активизировать. Это выполняется
циклически, причем каждый цикл состоит из трех шагов:
1) сопоставление фактов и правил;
2) выбор правила, подлежащего активизации;
3) выполнение действий, предписанных правилом.
Такой
трехшаговый
циклический
процесс
иногда
называют
«циклом распознавание – действие»
Сразу после запуска CLIPS-приложения на выполнение на экране
появится приглашение, извещающее пользователя, что он работает с
интерпретатором.
CLIPS>
В режиме интерпретатора пользователь может использовать множество
команд.
3.2. Простые типы данных
Для представления информации в CLIPS предусмотрено восемь
простых типов данных: float, integer, symbol, string, external-address, factaddress, instance-name и instance-address. Для представления числовой
36
информации используются типы
float и integer, символьной –
symbol и string. Остановимся на рассмотрении этих четырех типов данных.
При записи числа могут использоваться только цифры (0-9),
десятичная точка (.), знак (+) или (–) и (е) при экспоненциальном
представлении. Число сохраняется либо как целое, либо как действительное.
Любое число, состоящее только из цифр, перед которыми может стоять знак,
сохраняется как целое (тип integer представляется внутри CLIPS как тип
языка С long integer). Все остальные числа сохраняются как действительные
(float – С double float).
Количество значащих цифр зависит от аппаратной реализации. В этой
же связи могут возникать ошибки округления. Как в любом языке
программирования, особенную осторожность необходимо проявлять при
сравнении чисел с плавающей точкой, а также при сравнении с ними целых
чисел.
Примеры целых чисел:
237 15 +12 -32
Примеры чисел с плавающей точкой:
237е3 15.09 +12.0 -32.3е-7
Последовательность символов, которая не удовлетворяет числовым
типам, обрабатывается как тип данных symbol.
Тип данных symbol в CLIPS - последовательность символов, состоящая
из одного или нескольких любых печатных символов кода ASCII. Как только
в последовательности символов встречается символ-разделитель, symbol
заканчивается.
Следующие
символы
служат
разделителями:
любой
непечатный ASCII символ (включая пробел, символ табуляции, CR, LF),
двойные кавычки,"(",")", "&", "|", "<","~",";". Символы-разделители не могут
включаться в symbol за исключением символа "<", который может быть
первым символом в symbol. Кроме того, symbol не может начинаться с
символа "?" или последовательности символов "$?", поскольку эти символы
37
зарезервированы для переменных.
Заметим, что CLIPS различает
регистр символов. Ниже приведены примеры выражений символьного типа:
foo Hello B76-HI bad_value
127А 742-42-42 @+=-% Search
Тип данных string - это последовательность символов, состоящая из
нуля и более печатных символов и заключенная в двойные кавычки. Если
внутри строки встречаются двойные кавычки, то перед ними необходимо
поместить символ (\). То же справедливо и для самого (\). Несколько
примеров:
"foo" "a and b" "I number" "a\"quote"
Отметим, что строка "abcd" не тоже самое, что abcd. Они содержат
одина-ковые наборы символов, но являются экземплярами различного типа.
3.3. Функции
Под функцией в CLIPS понимается фрагмент исполняемого кода, с
которым связано уникальное имя и который возвращает полезное значение
или имеет полезный побочный эффект (например, вывод информации на
экран).
Существует несколько типов функций. Пользовательские и системные
функции - это фрагменты кода, написанные на внешних языках (например,
на С) и связанные со средой CLIPS. Системными называются те функции,
которые
были
определены
изначально
внутри
среды
CLIPS.
Пользовательскими называются функции, которые были определены вне
CLIPS.
Хотя CLIPS и не ориентирована на численные вычисления, в ней
предусмотрен ряд стандартных арифметических и математических функций.
Среди них:
+ Сложение
- Вычитание
* Умножение
/ Деление
38
* * Возведение в степень
Abs Определение абсолютного значения
Sqrt Вычисление квадратного корня
Mod Взятие по модулю
Min Нахождение минимума
Мах Нахождение максимума
3.4. Конструкции. Определение функций непосредственно в среде
CLIPS
В CLIPS существует несколько описывающих конструкций:
defmodule, defrule, deffacts, deftemplate, defglobal, deffunction, defclass,
definstances, defmessage-handler, defgeneric.
При записи все они заключаются в скобки. Определение конструкции
отличается от вызова функции главным образом по производимому эффекту.
Обычно вызов функции оставляет состояние среды CLIPS без изменений (за
рядом исключений, когда речь идет о функциях сброса, очистки, открытия
файла и т.п.). Определение конструкции, напротив, в точности направлено на
изменение состояния среды путем внесения изменений в базу знаний CLIPS.
В отличие от функций конструкции никогда не возвращают значений.
Все конструкции (за исключением defglobal) позволяют размещать
комментарии сразу вслед за именем конструкции. Кроме того, комментарии
могут вставляться в код CLIPS при помощи точки с запятой (;). Все, что
следует за (;) до конца строки, будет игнорироваться CLIPS. Если (;) стоит
первым символом в строке, то вся строка считается комментарием.
Конструкция deffunction позволяет пользователю определять новые
функции непосредственно в среде CLIPS с использованием синтаксиса
CLIPS. В языке CLIPS функции конструируются примерно так же, как в
языке LISP. Существенное отличие состоит в том, что переменные должны
иметь префикс ?, как это показано в приведенном ниже определении.
Функции, определенные таким образом, выглядят и работают подобно
39
остальным функциям, однако они
выполняются
не
напрямую,
а
интерпретируются средой CLIPS.
(deffunction hypotenuse (?a ?b)
(sqrt (+ (* ?a ?a) (* ?b ?b))
)
Формат определения функции в CLIPS следующий:
(deffunction <имя функции>
(<аргумент> …<аргумент>)
<выражение>
…………….
<выражение>
)
Функция возвращает результат последнего выражения в списке.
Иногда выполнение функции имеет побочные эффекты, как в приведенном
ниже примере.
(deffunction init (?day)
(reset)
(assert (today is ?day))
)
В результате после запуска функции на выполнение командой
CLIPS> (init Sunday)
Будет выполнена команда reset и, следовательно, очищена база фактов,
а затем в нее будет включен новый факт (today is Sunday).
Вызовы функций в CLIPS имеют префиксную форму: аргументы
функции могут стоять только после ее названия. Вызов функции начинается
с открывающейся скобки, за которой следует имя функции, затем идут
аргументы, каждый из которых отделен одним или несколькими пробелами.
Аргументами функции могут быть данные простых типов, переменные или
вызовы других функций. В конце вызова ставится закрывающаяся скобка.
Ниже приведены примеры вызовов функций:
(+345)
(* 5 6.02)
(+ 3 (* 8 9) 4)
(* 8 (+ 3 (* 2 3 4) 9) (* 3 4) )
40
3.5. Факты
Факты являются одной из основных форм представления информации
в системе CLIPS. Каждый факт представляет фрагмент информации, который
был помещен в текущий список фактов, называемый fact-list. Факт
представляет собой основную единицу данных, используемую правилами.
Количество фактов в списке и объем информации, который может быть
сохранен в факте, ограничивается только размером памяти компьютера. Если
при добавлении нового факта к списку обнаруживается, что он полностью
совпадает с одним из уже включенных в список фактов, то эта операция
игнорируется (хотя такое поведение можно изменить).
Факт может описываться индексом или адресом. Всякий раз, когда
факт
добавляется
(изменяется),
ему
присваивается
уникальный
целочисленный индекс. Индексы фактов начинаются с нуля и для каждого
нового или измененного факта увеличиваются на единицу. Каждый раз после
выполнения команд reset и clear выделение индексов начинается с нуля.
Факт также может задаваться при помощи адреса. Адрес факта может быть
получен путем сохранения возвращаемого значения команд, которые
возвращают в качестве результата адрес факта (таких как assert, modify и
duplicate), или путем связывания переменной с адресом факта в левой части
правила (см. далее).
Идентификатор факта – это короткая запись для отображения факта на
экране. Она состоит из символа f и записанного через тире индекса факта.
Например, запись f-10 служит для обозначения факта с индексом 10.
Существует
два
формата
представления
фактов:
позиционный
и
непозиционный.
Позиционные факты состоят из выражения символьного типа, за
которым
следует
последовательность
(возможно,
пустая)
из
полей,
разделенных пробелами. Вся запись заключается в скобки. Обычно первое
41
поле
определяет
"отношение",
которое
применяется
к
оставшимся полям. Например: (the pump is on) (altitude is 10000 feet)
(grocery_list bread milk eggs)
Поля в позиционных фактах могут быть любого простого типа (за
исключением первого поля, которое всегда должно быть типа symbol), на
порядок полей также не накладывается никаких ограничений. Следующие
символьные выражения зарезервированы и не должны использоваться как
первое поле любого факта (позиционного или нет): test, and, or, not, declare,
logical, object, exists u forall.
Для
того
чтобы
обратиться
к
информации,
содержащейся
в
позиционном факте, пользователь должен знать не только какие данные
содержатся в факте, но и то, в каком поле они хранятся. Непозиционные
(шаблонные) факты дают возможность пользователю абстрагироваться от
структуры факта, задавая имена каждому из полей факта. Для задания
шаблона, который затем может использоваться при доступе к полям по
именам, используется конструкция deftemplate. Эта конструкция подобна
структуре или записи в языках программирования С и Паскаль.
Конструкция
deftemplate
позволяет
наряду
с
определением
именованных полей, или слотов, вводить имя шаблона. В отличие от
позиционных фактов слоты шаблонного факта могут быть ограничены по
типу, значению, числовому диапазону. Кроме того, для любого слота можно
определить значения по умолчанию. Слот состоит из открывающейся скобки,
за которой следует имя слота, полей (могут отсутствовать) и закрывающейся
скобки. Заметим, что слоты не могут использоваться в позиционных фактах,
так же как позиционные поля не могут использоваться в шаблонных фактах.
Общая структура конструкции deftemplate такова:
(deftemplate )
(slot-1)
(slot-2)
…
(slot-N)
42
Далее
приведен
пример
шаблона с заданными для слотов
значениями по умолчанию:
(deftemplate prospect)
(slot name
(default ?DERIVE)
(slot. assets
(default rich)
(slot age
(default 80 )))
Шаблонные факты отличаются от позиционных по первому полю в
факте. Первое поле всех фактов должно быть типа symbol, но если это
символьное выражение соответствует имени шаблона, то этот факт шаблонный. За первым полем шаблонного факта следует список из нуля или
более слотов. Как и позиционные, шаблонные факты заключаются в скобки.
Далее приведено несколько примеров шаблонных фактов:
(client (name "Joe Brown") (id X9345A))
(point-mass (x-velocity 100)
(y-velocity -200))
(class (teacher "Martha Jones")
(#-students 30)
(room "37A"))
(grocery-list (#-of-items 3)
(items bread milk eggs))
Заметим, что порядок следования слотов в шаблонном факте не важен.
3.6. Манипуляции над фактами
Факты могут добавляться к списку фактов (с помощью команды assert),
удаляться из него (с помощью команды retract), изменяться (с помощью
команды modify) и дублироваться (с помощью команды duplicate) самим
пользователем или программой. Например:
CLIPS> (assert (today is Sunday))
CLIPS> (assert (weather is warm))
(assert (light green))
Для вывода списка фактов, имеющихся в базе, используется команда
facts:
43
CLIPS>(facts)
f-1 (today is Sunday)
f-2 (weather is warm)
В последних версиях CLIPS , в частности, в той, которая работает в
операционной среде Windows, такие команды как facts, можно вызвать с
помощью меню.
Кроме того, конструкция deffacts позволяет определить множество
исходных или априорных знаний в виде набора фактов. Например:
(deffacts walk "Some facts about walking"
(status walking)
(walk-sign walk) )
Для удаления фактов из базы используется команда retract.
CLIPS> (retract 1)
CLIPS> (facts)
f-0 (today is Sunday)
Эти же команды, assert и retract, используются в выполняемой части
правил (заключении правила) и с их помощью выполняется программное
изменение базы фактов. Часто приходится пользоваться и другой командой
интерпретатора, clear, которая очищает базу фактов (как правило, эта
команда доступна в одном из выпадающих меню).
CLIPS> (clear)
CLIPS> (facts)
В тексте программы факты можно включать в базу не по одиночке, а
целым массивом. Для этого в CLIPS имеется команда deffacts.
(deffacts today
(today is Sunday)
(weather is warm)
)
Выражение deffacts имеет формат, аналогичный выражениям в языке
LISP. Выражение начинается с команды deffacts, затем приводится имя
списка фактов, который программист собирается определить (в нашем
примере – today), а за ним следуют элементы списка, причем их количество
44
не ограничивается. Этот массив
фактов можно затем удалить из
базы командой undeffacts.
CLIPS> (undeffacts today)
Выражение
deffacts
можно
вводить
и
в
командную
строку
нтерпретатора, но лучше записать его в текстовый файл с помощью
редактора CLIPS или любого другого текстового редактора. Загрузить этот
файл в дальнейшем можно с помощью команды в меню File либо из
командной строки.
CLIPS> (load “my file”)
Однако после загрузки файла факты не передаются сразу же в базу
фактов CLIPS. Команда deffacts просто указывает интерпретатору, что
существует массив today, который содержит множество фактов. Собственно
загрузка выполняется командой reset.
CLIPS> (reset)
Когда производится сброс состояния среды CLIPS (с помощью
команды reset) все факты, описанные в конструкции deffacts, добавляются к
списку фактов. Кроме того, по этой команде в список фактов заносится
исходный факт (initial-fact). Этот факт включается в список фактов всегда с
идентификатором f-0. Его назначение будет рассмотрено в следующем
пункте.
3.7. Правила
Одним из основных методов представления знаний в CLIPS являются
правила. Правила используются для представления эвристик, определяющих
ряд действий, которые необходимо выполнить в определенной ситуации.
Разработчик экспертной системы определяет совокупность правил, которые
используются совместно для решения проблемы. Правило состоит из двух
частей: антецедента (условия), который является аналогом условия в if-then
операторе и записывается слева, и консеквента (заключения), который
является аналогом then части этого оператора и записывается справа.
45
Левая
часть
правила
представляет собой ряд условий
(условных элементов), которые должны выполняться, чтобы правило было
применимо. В CLIPS принято считать, что условие выполняется, если
соответствующий ему факт присутствует в списке фактов. Одним из типов
условных элементов может быть образец. Образцы состоят из набора
ограничений, которые используются для описания того, какие факты
удовлетворяют условию, определяемому образцом. Процесс сопоставления
фактов
и
образцов
выполняется
блоком
вывода
CLIPS,
который
автоматически сопоставляет образцы, исходя из текущего состояния списка
фактов, и определяет, какие из правил являются применимыми. Если все
условия правила выполняются, то оно активируется и помещается в список
активированных правил.
Если левая часть правила пуста, то для его активации необходимо
наличие в списке фактов исходного факта (initial-fact). Такие безусловные
правила часто используются для того, чтобы инициировать работу
программы.
Поэтому
перед
запуском
таких
программ
необходимо
произвести сброс состояния среды CLIPS.
Правая часть правила представляет собой совокупность действий,
которые должны быть выполнены, если правило применимо. Действия,
описанные в применимых правилах, выполняются тогда, когда блок вывода
CLIPS получает команду начать выполнение применимых правил. Если
существует множество применимых правил, то для того, чтобы выбрать
правило, действия которого должны быть выполнены, блок вывода
использует стратегию разрешения конфликтов. Действия, описанные в
выбранном правиле, выполняются (при этом список применимых правил
может измениться), а затем блок вывода выбирает другое правило и т.д. Этот
процесс продолжается до тех пор, пока не остается ни одного применимого
правила, т.е. пока список активированных правил не окажется пуст.
Во многом правила похожи на операторы типа if-then процедурных
языков программирования. Однако условие if-then оператора в процедурном
46
языке проверяется только тогда,
когда программа передает ему
управление.
иная.
С
правилами
ситуация
Блок
вывода
постоянно
отслеживает все правила, условия которых выполняются, и, таким образом,
правило может быть выполнено в любой момент, как только оно становится
применимым.
В
этом
смысле
правила
подобны
обработчикам
исключительных ситуаций в процедурных языках (например, в языке Ада).
Для определения правил используется конструкция defrule:
(defrule <имя правила>
< необязательный комментарий >
< необязательное объявление >
< предпосылка_1 >
……………….
< предпосылка_m >
=>
< действие_1 >
………………..
< предпосылка_n >
)
Например:
(defrule chores
“Things to do on Sunday”
(salience 10 )
(today is Sunday)
(weather is warm)
=>
(assert (wash car))
(assert (chop wood)
)
В этом примере chores – произвольно выбранное имя правила.
Предпосылки в условной части правила
(today is Sunday)
(weather is warm)
сопоставляются затем интерпретатором с базой фактов, а действия,
перечисленные в выполняемой части правила (она начинается после пары
символов =>), вставят в базу два факта
47
(wash car)
(chop wood)
в случае, если правило будет активизировано. Приведенный в тексте
правила комментарий
“Things to do on Sunday”
“Что делать в воскресенье”
поможет в дальнейшем вспомнить, чего ради это правило включено в
программу. Выражение
(salience 10)
указывает на степень важности правила. Пусть например, в программе
имеется другое правило
(defrule fun
“Better things to do on Sunday”
(salience 100)
(today is Sunday)
(weather is warm)
=>
(assert (drink beer))
(assert (play guitar))
)
Поскольку предпосылки обоих правил одинаковы, то при выполнении
оговоренных
условий
они
будут
«конкурировать»
за
внимание
интерпретатора. Предпочтение будет отдано правилу, у которого параметр
salience имеет более высокое значение, в данном случае – правилу fun.
Параметру salience может быть присвоено любое целочисленное значение в
диапазоне [-10000, 10000]. Если параметр salience в определении правила
опущен, ему по умолчанию присваивается значение 0.
3.8. Переменные
Как и в других языках программирования, в CLIPS для хранения
значений используются переменные. В отличие от фактов, которые являются
статическими, или неизменными, содержание переменной динамично и
изменяется по мере того, как изменяется присвоенное ей значение.
48
Идентификатор
переменной
всегда
начинается
с
вопросительного знака, за которым следует ее имя. В общем случае формат
переменной выглядит следующим образом:
?имя_переменной
Примеры переменных:
?х ?sensor ?noun ?color
Перед использованием переменной ей необходимо присвоить значение.
Все переменные, кроме глобальных, считаются локальными и могут
использоваться только в рамках описания конструкции. К этим локальным
переменным можно обращаться внутри описания, но они не определены вне
него. Чаще всего переменные описываются и получают значения в левой
части правила. Например:
(defrule make-quack
(duck-sound ?sound)
=>
(assert (sounds-is ?sound) )
Получив значение, переменная сохраняет его неизменным при
использовании как в левой, так и в правой части правила, если только это
значение не изменяется в правой части при помощи функции bind.
(defrule addition
(numbers ?x ?y)
=>
(assert (answer (+ ?x ?y)))
(bind ?answer (+ ?x ?y))
(printout t "answer is " ?answer crlf))
Другой пример:
(defrule pick-a-chore
“Allocating chores to days”
(today is ?day)
(chore is ?job)
=>
(assert (do ?job on ?day))
)
будет сопоставлено с фактами
49
(today is Sunday)
(chore is carwash)
то в случае активизации оно включит в базу новый факт
(do carwash on Sunday).
Аналогично, правило
(defrule drop-a-chore
“Allocating chores to days”
(today is ?day)
?chore <- (do ?job on ?day)
=>
(retract ?chore)
)
отменит выполнение работ по дому (a chore). Обратите внимание на то,
что оба экземпляра переменной ?day должны получить одно и то же
значение. Переменная ?chore в результате сопоставления должна получить
ссылку на факт, который мы собираемся исключить из базы. Таким образом,
если это правило будет сопоставлено с базой фактов, в которой содержатся
(today is Sunday)
(do carwash on Sunday)
то при активизации правила из базы будет удален факт
(do carwash on Sunday)
С
подробностями
интерпретаторе
CLIPS
выполнения
вы
сможете
процесса
сопоставления
познакомиться
в
Руководстве
пользователя , а здесь только отметим, что факт
(do carwash on Sunday)
будет сопоставлен с любым из представленных ниже образцов
(do ? ? Sunday)
(do ? on ?)
(do ? on ?when)
(do $?)
(do $? Sunday)
(do ?chore $?when)
в
50
Учтите,
что
префикс
$?
является признаком сегментной
переменной, которая будет связана с сегментом списка. Например, в
приведенном выше примере переменная $?when будет связана с (on Sunday)
Если за префиксами ? и $? не следует имя переменой, они
рассматриваются как
универсальные символы подстановки, которым
соответственно может быть сопоставлен любой элемент или сегмент списка.
Кроме значения самого факта, переменной может быть присвоено
значение адреса факта. Это может оказаться удобным при необходимости
манипулировать
присвоения
фактами
используется
непосредственно
комбинация
из
"<-".
правила.
Для
Следующий
такого
пример
иллюстрирует присвоение переменной значения адреса факта и ее
последующее использование:
(defrule get-married
?duck <- (bachelor Dopey)
=>
(retract ?duck))
Для определения глобальных переменных, которые видны всюду в
среде CLIPS, используется конструкция defglobal. К глобальной переменной
можно обратиться в любом месте, и ее значение остается независимым от
других конструкций. Глобальные переменные CLIPS подобны глобальным
переменным в процедурных языках программирования, но они значительно
слабее типизированы (на них не налагается ограничения хранения данных
только одного типа).
3.9. Использование шаблонов
Для определения фактов можно использовать не только списочные
структуры, но и шаблоны, которые напоминают простые записи. (Шаблоны в
CLIPS не имеют ничего общего с шаблонами C++.) Шаблон выглядит
примерно так:
51
(deftemplate student “cmt”
(slot name (type STRING))
(slot age (type NUMBER) (default 18))
)
Каждое определение шаблона состоит из произвольного имени
шаблона,
необязательного
комментария
и
некоторого
количества
определений слотов. Слот включает поле данных, например name, и тип
данных, например STRING. Можно указать и значение по умолчанию, как в
приведенном выше примере.
Если в программу включено приведенное выше определение шаблона,
то выражение
(deffacts students
(student (name fred))
(student (name freda) (age 19))
)
приведет к тому, что в базу фактов после выполнения команды reset
будет добавлено
(student (name fred) (age 18))
(student (name freda) (age 19))
3.10. Объектно-ориентированные средства в CLIPS
Использование объектно-ориентированных средств в CLIPS позволяет
значительно упростить программирование правил, поскольку для обновления
данных можно применять механизм передачи и обработки сообщений
методами классов. В этом разделе мы продемонстрируем, как это делается
на примере, который моделирует правила обращения с полуавтоматическим
пистолетом.
Определим класс pistol, в котором будут перечислены свойства,
необходимые для моделирования.
(defclass pistol
(is-a USER)
(role concrete)
(pattern-match reactive)
52
(slot safety (type SYMBOL)
(create-accessor read-write))
(slot slide (type SYMBOL)
(create-accessor read-write))
(slot hammer (type SYMBOL)
(create-accessor read-write))
(slot chamber (type INTEGER)
(create-accessor read-write))
(slot magazine (type SYMBOL)
(create-accessor read-write))
(slot rounds (type INTEGER)
(create-accessor read-write))
)
Первые три слота – системные. Они нужны объектно-ориентированной
надстройке CLIPS (COOL – CLIPS object-oriented language). Эти слоты COOL
извещают о том, что pistol – это пользовательский класс; pistol является
конкретным классом, т.е. возможно создание экземпляров этого класса
(альтернативный тип – абстрактный класс, который играет ту же роль, что и
виртуальный
класс в C++);
экземпляры класса
pistol
могут быть
использованы в качестве объектов данных, которые можно сопоставлять с
условиями в правилах и
использовать в действиях, определенных
правилами.
Следующие пять слотов представляют свойства и члены данных
класса:
– слот safety (предохранитель) может содержать символ on или off;
– слот slide (затвор) может содержать значение forward или back,
т.е. хранит информацию о положении затвора;
– слот hammer (курок) содержит информацию о состоянии курка ,
back или down;
– слот chamber (патронник) содержит значение 1
или
0,
в
зависимости от того, есть ли патрон в патроннике;
– слот magazine (обойма) может содержать значение in или out, в
зависимости от того, вставлена ли обойма;
53
– слот
rounds
(патроны)
содержит
текущее
количество
патронов в обойме.
Для того чтобы иметь возможность записывать в слот новое значение
или считывать текущее, нужно разрешить формирование соответствующих
функций доступа через фацет create-accessor. Теперь сформируем экземпляр
класса pistol с помощью следующего выражения:
(definstances pistols
(PPK of pistol
(safety on)
(slide forward)
(hammer down)
(chamber 0)
(magazine out)
(rounds 6)
)
)
Этот экземпляр, PPK, правильно уложен – обойма вынута из рукоятки,
пистолет установлен на предохранитель, затвор в переднем
положении,
курок опущен, а патронник пуст. В обойме имеется 6 патронов. Теперь, имея
в программе определение класса и сформировав экземпляр класса,
разработаем правила и обработчики сообщений, с помощью которых можно
описать отдельные операции обращения с пистолетом и стрельбы из него.
Для этого сначала разработаем шаблон задачи. Желательно отслеживать две
вещи:
– есть ли патрон в патроннике;
– произведен ли выстрел.
Для этого можно использовать следующий шаблон:
(deftemplate range-test
(field check (type SYMBOL) (default no))
(field fired (type SYMBOL) (default no))
)
Первое правило будет устанавливать в рабочую память программы
задачу range-test.
54
(defrule start
(initial-fact)
=>
(assert (range-test))
)
При активизации этого правила в рабочую память будет добавлено
(range-test (check no) (fired no))
Следующие три правила будут проверять правильно ли снаряжен
пистолет.
(defrule check
(object (name [PPK]) (safety on)
(magazine out))
?T<- (range-test (check no))
=>
(send [PPK] clear)
(modify ?T (check yes)
)
Правило check заключается в том, что если пистолет стоит на
предохранителе (safety on), обойма вынута (magazine out) и пистолет не был
проверен, то нужно очистить патронник и проверить, нет ли в нем патрона.
Обработчик сообщений clear для класса pistol будет выглядеть
следующим образом:
(defmassage-handler pistol clear ()
(dynamic-put chamber 0)
(ppinstance)
)
В первой строке объявляется, что clear является обработчиком
сообщения для класса pistol, причем этот обработчик не требует передачи
аргументов. Оператор во второй строке «очищает» патронник. Присвоение
выполняется независимо от того, какое текущее значение имеет слот
chamber, 0 или 1. Оператор в третьей строке требует, чтобы экземпляр
распечатал информацию о текущем состоянии своих слотов.
В следующих двух правилах обрабатываются ситуации, когда пистолет
снаряжен неправильно, – не установлен на предохранитель или в него
55
вставлена
обойма.
Правило
correct1 устанавливает пистолет
на предохранитель, а правило correct2 извлекает из него обойму.
(defrule correct1
(object (name [PPK])
(safety off) )
(range-test (check no))
=>
(send [PPK] safety on)
)
(defrule correct2
(object (name [PPK]) (safety on)
(magazine in))
(range-test (check no))
=>
(send [PPK] drop)
)
Как
при
разработке
предыдущего
правила,
нам
понадобятся
обработчики сообщений safety и drop.
(defmessage-handler pistol safety (?on-off)
(dynamic-put safety ?on-off)
(if (eq ?on-off on)
then (dynamic-put hammer down)
)
)
Обработчик сообщения safety принимает единственный аргумент,
который может иметь только два символических значения on или off. В
противном случае нам пришлось бы разработать два обработчика: один для
сообщения safety-on, а другой – для сообщения safety-off. Учтите, что в
некоторых моделях, например в Walther PPK, при установке пистолета на
предохранитель патронник очищается автоматически.
Обработчик сообщения drop просто извлекает обойму из пистолета.
(defmessage-handler pistol drop ()
(dynamic-put magazine out)
)
56
Теперь, когда обеспечено
правильное исходное снаряжение
пистолета, можно приступить к стрельбе. Следующее правило обеспечивает
вставку обоймы в пистолет перед стрельбой:
(defrule mag-in
(object (name [PPK])
(safety on ) (magazine out))
(range-test (fired no) (check yes))
=>
(send [PPK] seat)
)
Обработчик сообщения seat выполняет действия, противоположные
тем, которые выполняет обработчик drop.
(defmessage-handler pistol seat ()
(dynamic-put magazine in)
)
Можно было бы, конечно, включить в программу и следующее правило
mag-in:
(defrule mag-in
?gun <- (object (name [PPK])
(safety on ) (magazine
out))
(range-test (fired no) (check yes))
=>
(modify ?gun (magazine in)
)
но это противоречит одному из принципов объектно-ориентированного
программирования, который гласит, что объект должен самостоятельно
обрабатывать
содержащиеся
в
нем
данные.
обеспечивает снаряжение обоймы патронами:
(defrule load
(object (name [PPK])
(magazine in) (chamber 0))
=>
(send [PPK] rack)
)
Следующее
правило
57
На
примере
обработчика
сообщения
rack
вы
можете
убедиться в справедливости замечания о том, что обработку данных внутри
объекта нужно поручать методам этого объекта, а не включать прямо в
правило.
(defmessage-handler pistol rack ()
(if (> (dynamic-get rounds) 0)
then (dynamic-put chamber 1)
(dynamic-put rounds
(- (dynamic-get rounds) 1))
(dynamic-put slide forward)
else (dynamic-put chamber 0)
(dynamic-put slide back)
)
)
В этом обработчике обеспечивается досылка патрона в патронник в
том случае, если в обойме имеются патроны. Следующее правило
подготавливает пистолет к стрельбе, снимая его с предохранителя. Обратите
внимание на то, что в нем повторно используется сообщение safety, но на
этот раз с аргументом off.
(defrule ready
(object (name [PPK]) (chamber 1))
=>
(send [PPK] safety off)
)
Правило fire выполняет стрельбу.
(defrule fire
(object (name [PPK]) (safety off);
?T <- (range-test (fired no))
=>
(if (eq (send [PPK] fire) TRUE)
then (modify ?T (fired yes)))
)
Обратите внимание, что в данном правиле используется обработчик
сообщения, которое возвращает значение. Анализируя его, можно выяснить,
произведен ли выстрел, т.е. выполнена ли в действительности та операция,
58
которая
«закреплена» за этим
сообщением. Если в патроннике
был патрон и пистолет был снят с предохранителя, то обработчик сообщения
вернет значение TRUE (после того, как выведет на экран BANG!). В
противном случае он вернет FALSE (после того, как выведет на экран click).
(defmessage-handler pistol fire ()
(if (and
(eq (dynamic-get chamber) 1)
(eq (dynamic-get safety) off)
)
then (printout t crlf “BANG!” t crlf)
TRUE
else (printout t crlf “click” t crlf)
FALSE
)
)
Пусть вас не смущает, что в обработчике сообщения анализируется
условие, которое уже было проанализировано правилом, отославшим
сообщение (в данном случае речь идет об условии safety off). Дело в том, что
одно и тоже сообщение может отсылаться разными правилами и нет никакой
гарантии, что в каждом из них будет проверяться это условие.
После завершения стрельбы пистолет нужно вновь вернуть в
положение
«по-походному».
устанавливается
на
Начинается
предохранитель,
это
для
с
чего
того,
что
пистолет
используется
ранее
разработанный обработчик сообщения safety.
(defrule unready
(object (name [PPK]) (safety off))
(range-test (fired yes))
=>
(send [PPK] safety on)
)
Следующая операция – вынуть обойму. Обратите внимание, что в нем
мы вновь обращаемся к обработчику сообщения drop.
(defrule drop
(object (name [PPK]) (safety on))
59
(range-test (fired
yes))
=>
(send [PPK] drop)
)
Последнее правило выбрасывает патрон из патронника, вызывая
обработчик сообщения clear.
(defrule unload
(object (name [PPK]) (safety on)
(magazine out))
range-test (fired yes))
=>
(send [PPK] clear)
)
В этом примере было продемонстрировано, как в рамках единой CLIPS
программы “уживаются” правила и объекты. Правила управляют ходом
вычислений, но некоторые операции объекты выполняют и самостоятельно,
получив “указание” (сообщение) от правил. Объекты не являются
резидентами рабочей памяти, но члены левой части правил (условий) могут
быть сопоставлены с содержимым их слотов. Состояние объектов может
измениться и вследствие побочных эффектов активизации правил, но лучше
предоставить
объектам
возможность
самостоятельно
выполнять
манипуляции с хранящимися в них данными в ответ на поступающие от
правил сообщения.
Объекты не могут самостоятельно активизировать правила, но их
обработчики сообщения могут “возвращать” определенную информацию о
результатах, которая используется для управления логикой выполнения
действий в правой части правил.
3.11. Стиль программирования на языке CLIPS
Большинство рекомендаций, относящихся к методике проектирования
систем, основанных на правилах, сохраняют свою силу и при использовании
в качестве основного инструмента
проектирования языка CLIPS. В
60
частности,
работая
с
CLIPS,
нужно стараться так организовать
систему правил, чтобы каждое из них было как можно проще. Как и при
программировании любых других задач, ключевым условием разработки
«хорошего» программного кода является правильный выбор набора
абстрактных понятий, которыми
должна манипулировать программа, и
набора операций, которые она должна выполнять. Первое условие поможет
рационально выбрать структуру объектов и форму представления условий в
левой части правил, а второе – рационально организовать действия в правой
части. Использование объектов и обработчиков сообщений позволяет
успешно решить задачу рациональной организации данных и процедур в
программе.
Более подробные сведения о среде и языке CLIPS можно найти в
свободно распространяемой документации на неё (на английском языке) или
в специальной литературе, в частности в частях II-IV книги [7].
61
Библиографический список
1. Адаменко, А.Н. Логическое программирование и Visual Prolog /
А.Н.Адаменко, А.М.Кучуков. СПб.: БХВ-Петербург, 2003. 992 с.
2. Братко, Иван. Алгоритмы искусственного интеллекта на языке
PROLOG, 3-е издание. : Пер. с англ. / Иван Братко. М.: Издательский дом
«Вильямс», 2004. 640 с.
3. Люгер, Джордж, Ф. Искусственный интеллект: стратегии и методы
решения сложных проблем, 4-е издание. : Пер. с англ. / Джордж Ф. Люгер.
М.: Издательский дом «Вильямс», 2003. 864 с.
4. Городняя, Л.В. Основы функционального программирования: курс
лекций. учеб. пос. / Л.В. Городняя. М.: ИНТУИТюРУ «Интернет-университет
Информационных технологий», 2004. 280 с.
5. Хьюванен, Э. Мир Лиспа. Том 1. Введение в язык Лисп и
функциональное программирование. : Пер. с финск. / Э. Хьюванен,
Й. Сеппянен. М.: Мир, 1990. 447 с.
6. Хьюванен, Э. Мир Лиспа. Том 2. Методы и системы
программирования. : Пер. с финск. / Э. Хьюванен, Й. Сеппянен. М.: Мир,
1990. 332 с.
7. Частиков, А.П. Разработка экспертных систем. Среда CLIPS. / А.П.
Частиков, Т.А. Гаврилова, Д.Л. Белов. СПб.: БХВ-Петербург, 2003. 608 с.
62
ОГЛАВЛЕНИЕ
ВВЕДЕНИЕ.................................................................................................... 3
1. ЯЗЫК ПРОГРАММИРОВАНИЯ PROLOG ........................................... 5
1.1. Основы языка Пролог ........................................................................ 5
1.2. Типы предложений в языке PROLOG ............................................. 6
1.3. Унификация переменных. ................................................................. 9
1.4. Списки ............................................................................................... 12
1.5. Откат (backtrace)............................................................................... 14
1.6. Предикат ! (отсечение) .................................................................... 14
1.7. Представление данных структурами. ............................................ 16
1.8. Обновление базы данных Пролога................................................. 16
2. ЯЗЫК ПРОГРАММИРОВАНИЯ LISP ................................................. 18
2.1. История создания ЯП LISP. Парадигма функционального
программирования. ........................................................................................... 18
2.2. Основные особенности ЯП LISP .................................................... 19
2.3. Символы и их значения ................................................................... 20
2.4. Структуры данных ........................................................................... 21
2.5. Функции. Запись функций и программ ......................................... 21
2.6. Функции с побочным эффектом..................................................... 23
2.7. Базовые функции.............................................................................. 24
2.8. Предикаты ......................................................................................... 26
2.9. Определения функций. Формализм их задания ............................ 29
2.10. Функционалы.................................................................................. 31
2.11. Свойства символов......................................................................... 32
2.12. Основные недостатки языка Лисп ............................................... 33
3. СРЕДА CLIPS .......................................................................................... 34
3.1. Общие сведения и краткая история среды CLIPS ........................ 34
3.2. Простые типы данных ..................................................................... 35
3.3. Функции ............................................................................................ 37
3.4. Конструкции. Определение функций непосредственно в среде
CLIPS .................................................................................................................. 38
3.5. Факты ................................................................................................ 40
3.6. Манипуляции над фактами ............................................................. 42
3.7. Правила ............................................................................................. 44
3.8. Переменные ...................................................................................... 47
3.9. Использование шаблонов ................................................................ 50
3.10. Объектно-ориентированные средства в CLIPS........................... 51
3.11. Стиль программирования на языке CLIPS .................................. 59
Библиографический список ............................................................... 61
Download