Работа с базой данных
Реляционная модель предполагает, что база данных - это описание некоторого множества отношений. Пролог-программу можно рассматривать как именно такую базу данных: описание отношений частично присутствует в ней в явном виде (факты), а частично - в неявном (правила). Более того, встроенные предикаты дают возможность корректировать эту базу данных в процессе выполнения программ. Это делается добавлением к программе (в процессе вычисления) новых предложений или же вычеркиванием из нее уже существующих. Предикаты, используемые для этой цели, таковы: assert (добавить), asserta, assertz и retract (удалить).
Цель
assert( С)
всегда успешна, а в качестве своего побочного эффекта вызывает "констатацию" предложения С, т. е. добавление его к базе данных. Цель
retract( С)
приводит к противоположному эффекту: удаляет предложение, сопоставимое с С. Следующий диалог иллюстрирует их работу:
?- кризис.
no
? - assert( кризис).
yes
?- кризис.
yes
? - retract( кризис).
yes
?- кризис.
no
Предложения, добавленные к программе таким способом, ведут себя точно так же, как и те, что были в "оригинале" программы. Следующий пример показывает, как с помощью assert и retract можно работать в условиях изменяющейся обстановки. Предположим, что у нас есть такая программа о погоде:
хорошая :-
солнечно, not дождь.
необычная :-
солнечно, дождь.
отвратительная :-
дождь, туман.
дождь.
туман.
Ниже приводится пример диалога с этой программой, во время которого база данных постепенно изменяется:
?- хорошая.
no
?- отвратительная.
yes
?- retract( туман).
yes
?- отвратительная.
no
?- assert( солнечно).
yes
?- необычная.
yes
?- retract( дождь).
уes
?- хорошая.
yes
Добавлять и удалять можно предложения любой формы. Следующий пример показывает, что, кроме того, retract может работать недетерминировано: используя механизм возвратов с помощью только одной цели retract можно удалить целое множество предложений. Предположим, что в программе, с которой мы "консультируемся", есть такие факты:
быстр( энн).
медл( том).
медл( пат).
К этой программе можно добавить правило:
?- assert(
( быстрее( X, Y) :-
быстр( X), медл( Y) ) ).
yes
?- быстрее( А, В).
А = энн
В = том
?- retract( медл( X) ).
Х = том;
X = пат;
nо
?- быстрее( энн, _ ).
nо
Заметьте, что при добавлении нового правила синтаксис требует, чтобы оно (как аргумент assert) было заключено в скобки.
При добавлении нового предложения может возникнуть желание указать, на какое место в базе данных его следует поместить. Такую возможность обеспечивают предикаты asserta и assertz. Цель
asserta( С)
помещает С в начале базы данных. Цель
assertz( С)
- в конце. Вот пример, иллюстрирующий работу этих предикатов:
?- assеrt( р( a)), assertz( р( b) ), asserta( p( c) ).
yes
?- p( X).
Х = с;
Х = а;
Х = b
Между consult и assertz существует связь. Обращение к файлу при помощи consult можно в терминах assertz определить так: считать все термы (предложения) файла и добавить их в конец базы данных.
Одним из полезных применений предиката asserta является накопление уже вычисленных ответов на вопросы.
Пусть, например, в программе определен предикат
решить( Задача, Решение)
Мы можем теперь задать вопрос и потребовать, чтобы ответ на него был запомнен, с тем чтобы облегчить получение ответов на будущие вопросы:
?- решить( задача1, решение),
asserta( решить( Задача1, Решение) ).
Если в первой из приведенных целей будет успех, ответ ( Решение) будет сохранен, а затем использован так же, как и любое другое предложение, при ответе на дальнейшие вопросы. Преимущество такого "запоминания" состоит в том, что на дальнейшие вопросы, сопоставимые с добавленным фактом, ответ будет получен, как правило, значительно быстрее, чем в первый раз. Ответ будет теперь получен как факт, а не как результат вычислений, требующих, возможно, длительного времени.
Развитие этой идеи состоит в использовании assert для порождения всех решений в виде таблицы фактов. Например, создать таблицу произведений всех чисел от 0 до 9 можно так: породить пару чисел Х и Y, вычислить Z, равное Х * Y, добавить эти три числа в виде строки в таблицу произведений, а затем создать искусственно неуспех. Неуспех вызовет возврат, в результате которого будет найдена новая пара чисел, и в таблицу добавится новая строка и т.д. Эта идея реализована в процедуре
таблица :-
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
принадлежит( X, L), % Выбрать первый сомножитель
принадлежит( Y, L), % Выбрать второй сомножитель
Z is X*Y,
assert( произв( X,Y,Z) ),
fail.
Вопрос
?- таблица.
потерпит, конечно, неудачу, однако в качестве своего побочного эффекта приведет к добавлению в базу данных целой таблицы произведений. После этого можно, например, спросить, какие пары дают произведения, равные 8:
?- произв( А, В, 8).
А = 1
В = 8;
А = 2
В = 4;
. . .
Здесь следует сделать одно замечание, относящееся к стилю программирования. Приведенные примеры показали некоторые явно полезные применения assert и retract. Однако использование этих отношений требует особой внимательности. Не рекомендуется применять их слишком часто и без должной осторожности - это плохой стиль программирования. Ведь добавляя и удаляя предложения, мы фактически изменяем программу. Поэтому отношения, выполнявшиеся в некоторой ее точке, могут оказаться неверными в другой. В разные моменты времени ответы на одни и те же вопросы будут различными. Таким образом, большое количество обращений к assert и retract может затемнить смысл программы и станет трудно разобрать, что истинно, а что - нет. В результате поведение программы может стать непонятным, трудно объяснимым, и вряд ли можно будет ей доверять.