Смекни!
smekni.com

Программирование на языке CLIPS (стр. 2 из 12)

(on Sunday)

Если за префиксами ? и $? не следует имя переменой , они рассматриваются как универсальные символы подстановки, которым соответственно может быть сопоставлен любой элемент или сегмент списка.

А.2.3. Наблюдение за процессом интерпретации

Теперь на простом примере познакомимся с возможностями, которые предоставляет среда разработки CLIPS в части отладки программы, состоящей из правил и фактов. Введите в текстовый файл правило, а затем загрузите этот файл в среду CLIPS.

(defrule start

(initial-fact)

=>

(printout t “hello, world” crlf)

)

Выполните команду reset. Для этого введите эту команду в командной строке интерпретатора

CLIPS> (reset)

Либо выберите в меню команду Execution=>Reset, либо нажмите <CTRL+U> (последних два варианта возможны в версии, которая работает под Windows).

Затем запустите интерпретатор. Для этого введите эту команду run в командную строку интерпретатора

CLIPS> (run)

Либо выберите в меню команду Execution=>Run, либо нажмите <CTRL+R> (последних два варианта возможны в версии, которая работает под Windows).

В ответ программа должна вывести сообщение hello, world, знакомое всем программистам мира. Для повторного запуска программы повторите команды reset и run.

Если в меню Execution=>Watch ранее был установлен флажок Rules или перед запуском программы на выполнение вы ввели в командную стоку команду watch rules, то на экране появиться результат транссировки процесса выполнения

CLIPS> (run)

FIRE 1 start: f-0

hello, world

В этом сообщении в строке, начинающейся с FIRE, выведена информация об активизированном правиле: start – это имя правила, а f-0 – имя факта, который «удовлетворил» условие в этом правиле. Команда watch позволяет организовать несколько разных режимов трассировки, с деталями которых вы можете познакомиться в Руководстве пользователя. Если перед запуском программы вы ввели

CLIPS> (dribble-on “dribble.dp”)

TRUE

То выведенный протокол трассировки будет сохранен в файле dribble.clp. Сохранение протокола прекратится после ввода команда

CLIPS> (dribble-off)

TRUE

Это очень удобная опция, особенно на этапе освоения языка.

А.2.4. Использование шаблонов

Для определения фактов можно использовать не только списочные структуры, но и шаблоны, которые напоминают простые записи. (Шаблоны в CLIPS не имеют ничего общего с шаблонами C++.) Шаблон выглядит примерно так:

(deftemplate student “a student record”

(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))

А.2.5. Определение функций

В языке CLIPS функции конструируются примерно так же, как в языке LIPS. Существенное отличие состоит в том, что переменные должны иметь префикс ?, как это показано в приведенном ниже определении.

(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).

А.3. ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ СРЕДСТВА В CLIPS

Использование объектно-ориентированных средств в CLIPS позволяет значительно упростить программирование правил, поскольку для обновления данных можно применять механизм передачи и обработки сообщений методами классов. В этом разделе мы продемонстрируем, как это делается на примере, который моделирует правила обращения с полуавтоматическим пистолетом.

Первым делом определим класс pistol, в котором будут перечислены свойства, необходимые для моделирования.

(defclass pistol

(is-a USER)

(role concrete)

(pattern-match reactive)

(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, в зависимости от того, вставлена ли обойма;

· слот 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.

(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. Оператор в третьей строке требует, чтобы экземпляр распечатал информацию о текущем состоянии своих слотов.

В следующих двух правилах обрабатываются ситуации, когда пистолет снаряжен неправильно, - не установлен на предохранитель или в него вставлена обойма. Правило 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)

)

Теперь, когда обеспечено правильное исходное снаряжение пистолета, можно приступить к стрельбе. Следующее правило обеспечивает вставку обоймы в пистолет перед стрельбой:

(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)

)

но это противоречит одному из принципов объектно-ориентированного программирования, который гласит, что объект должен самостоятельно обрабатывать содержащиеся в нем данные. Следующее правило обеспечивает снаряжение обоймы патронами: