Волченков Логическое программирование язык пролог 2015
.pdfте же предикаты assert/1 и retract/1, менять значение этой переменной, уничтожить её, когда необходимость в ней исчезнет.
Ниже (код 8.2) приводятся определения некоторых полезных предикатов для работы с такими глобальными переменными (естественно, можно придумать и еще какие-нибудь):
ini_bag/2 – создать переменную с данным именем и данным начальным значением,
ini_bag/1 – создать переменную с данным именем и пустым списком в качестве начального значения,
dob_bag/3 – добавить в список новый элемент, выдать старое значение списка,
sum_bag/3 – прибавить к числовому значению переменной данное число, выдать старое значение,
inc_bag/2 – прибавить единицу, выдать старое значение, dec_bag/2 – вычесть единицу, выдать старое значение, zam_bag/3 – заменить старое значение новым,
fin_bag/2 – уничтожить переменную с данным именем и выдать
ее значение.
Код 8.2
ini_bag(Bag, Value) :- assert(bag(Bag, Value)). ini_bag(Bag) :- assert(bag(Bag, [])).
dob_bag(Bag, New, Old) :- retract(bag(Bag, Old)), assert(bag(Bag, [New|Old])), !.
sum_bag(Bag, N, Old) :- retract(bag(Bag, Old)), New is Old + N, assert(bag(Bag, New)), !.
inc_bag(Bag, Old) :- retract(bag(Bag, Old)), inc(Old, New), assert(bag(Bag, New)), !.
111
dec_bag(Bag, Old) :- retract(bag(Bag, Old)), dec(Old, New), assert(bag(Bag, New)), !.
zam_bag(Bag, New, Old) :- retract(bag(Bag, Old)), assert(bag(Bag, New)), !.
fin_bag(Bag, Value) :- retract(bag(Bag, Value)).
Пример 8.2. В качестве примера применения глобальных переменных рассмотрим задачу обработки отношения, представленного набором фактов, с целью получения некоторых характеристик этого отношения, в частности:
максимального значения данного числового аргумента,
суммы значений данного числового аргумента,
списка значений данного нечислового аргумента.
Для определенности, рассмотрим конкретное отношение книга/10, содержащее библиотечные данные о книгах. Помимо самого отношения, база данных содержит факт, позволяющий судить о структуре этого отношения (в частности, об именах аргументов или атрибутов):
rel_str(книга(шифp(_), автоp(_), название(_),
томов(_), том(_), стpаниц(_), год(_), издательство(_), гоpод(_), аннотация(_))).
Анонимные переменные в этой структуре изображают «ячейки памяти», в которых могут храниться какие-нибудь сведения об аргументах, например их тип, шаблон или что-то еще.
Пусть отношение содержит следующие факты (код 8.3):
Код 8.3
книга(7008991, 'Пушкин А.С.', 'Евгений Онегин. Поэмы', 1, 1, 368, 1970, 'Художественная Литература', 'Москва', _).
книга(7805540, 'Пушкин А.С.', 'Стихотворения. Поэмы', 1, 1, 416, 1978, 'Русский язык', 'Москва', _).
112
книга(8606115, 'Булгаков М.А.', 'Мастеp и Маpгаpита', 1, 1, 416, 1986, 'Лиесма', 'Рига', _).
книга(8711803, 'Достоевский Ф.М.', 'Подpосток',
1, 1, 576, 1987, 'Московский pабочий', 'Москва', _).
книга(9009366, 'Достоевский Ф.М.', 'Бесы',
1, 1, 672, 1990, 'Художественная литеpатуpа', 'Москва', _).
книга(9110577, 'Достоевский Ф.М.', 'Бpатья Каpамазовы', 2, 1, 416, 1991, 'Пpавда', 'Москва', _).
книга(9110578, 'Достоевский Ф.М.', 'Бpатья Каpамазовы',
2, 2, 529, 1991, 'Пpавда', 'Москва', _).
Допустим, что нужно найти книгу с максимальным числом страниц, общее число страниц во всех имеющихся книгах, а также список авторов всех книг.
Эту задачу легко решить на Прологе с использованием представленных выше глобальных переменных. Для этого создадим три глобальные переменные с именами max, itog и avtor_list.
Начальные значения этих переменных (_, 0), 0, []. Их конечные значения:
пара, состоящая из шифра книги с максимальным числом страниц и этого числа;
общее число страниц всех книг;
список авторов всех книг.
Программа на Прологе для решения данной задачи такова:
Код 8.4
itogi(Res1, Res2, Res3):-
ini_bag(max, (_,0)), ini_bag(itog, 0), ini_bag(avtor_list),
cycle,
fin_bag(max, Res1), fin_bag(itog, Res2), fin_bag(avtor_list, List),
sort(List, Res3). cycle:-
'книга'(A1, A2, _, _, _, A6, _, _, _, _), max(A1, A6),
113
itog(A6), avtor_list(A2), fail.
cycle :- !.
max(Sh, Str):- bag(max, (_, Old)), Str > Old, !,
zam_bag(max, (Sh, Str), (_, Old)). max(_,_):-!.
itog(Str):- sum_bag(itog, Str, _).
avtor_list(Avt):- dob_bag(avtor_list, Avt, _).
Обратившись к Прологу с запросом
?- itogi(Res1, Res2, Res3).
для приведенного множества книг получим следующее решение
данной задачи:
Res1 = (9009366, 672) Res2 = 3393
Res3 = ['Булгаков М.А.', 'Достоевский Ф.М.', 'Пушкин А.С.'].
Отметим, что в данной программе используется возвратный способ организации цикла cycle – с помощью предиката fail. При этом предикаты max/2, itog/1, avtor_list/1 должны работать безвозвратно, а по возврату происходит обращение лишь к фактам книга/10 с просмотром всего этого отношения.
4. Создание утилит для работы с базой данных
В приведённом выше примере очевидна одна негативная особенность: при написании определения предиката cycle программист был вынужден обращаться к довольно громоздкой структуре отношения, выделяя нужные аргументы, заключенные в ее глубине. Было бы еще хуже, если бы запросы к библиотечной базе дан-
114
ных понадобилось составлять непрограммирующему пользователю.
Пример 8.3. Рассмотрим следующие запросы к библиотечной базе данных:
«Найти авторов книг, изданных в городе Рига»,
«Указать названия, авторов и издательства двухтомных изданий»,
«Какие книги Пушкина А.С., изданные до 1990 года, имеются в базе данных?»,
«Книги каких немосковских издательств имеются в базе дан-
ных?».
Пользователь вынужден указывать те места в структуре книга/10, в которых находятся интересующие его атрибуты.
Естественным желанием программиста – составителя определений на Прологе – является освободить пользователя от необходимости знать структуру отношения и предоставить ему полезные утилиты для работы с базой данных.
Следующая программа (код 8.5) демонстрирует возможность
работы с такого рода утилитами.
Код 8.5
book_menu:-
book_attrib(L), book_menu(L).
book_menu([]):-
write('Шифр - шифр книги (ключ отношения)'), !.
book_menu([A|T]):- write(A), nl, book_menu(T).
book_attrib(['автоp(Шифр, Автор)',
'название(Шифр, Название)', 'томов(Шифр, КоличТомов)', 'том(Шифр, Том)', 'стpаниц(Шифр, ЧисСтраниц)', 'год(Шифр, Год)',
'издательство(Шифр, Издательство)',
115
'гоpод(Шифр, Город)',
|
'аннотация(Шифр, Аннотация)']). |
book(X):- |
|
% |
functor(X, книга, 10), call(X). |
Утилиты для пользователя: |
автоp(Sh, Av) :- book(X), arg(1, X, Sh), arg(2, X, Av). название(Sh, Naz) :- book(X), arg(1, X, Sh), arg(3, X, Naz). томов(Sh, TT) :- book(X), arg(1, X, Sh), arg(4, X, TT). том(Sh, T) :- book(X), arg(1, X, Sh), arg(5, X, T). стpаниц(Sh, Str) :- book(X), arg(1, X, Sh), arg(6, X, Str). год(Sh, God) :- book(X), arg(1, X, Sh), arg(7, X, God). издательство(Sh, Izd) :- book(X), arg(1, X, Sh), arg(8, X, Izd). гоpод(Sh, Gor) :- book(X), arg(1, X, Sh), arg(9, X, Gor). аннотация(Sh, Ann) :- book(X), arg(1, X, Sh), arg(10, X, Ann).
С использованием утилит перечисленные выше примеры запросов пользователя к библиотечной базе данных легко записать в виде следующих целей Пролога:
?- город(S, 'Рига'), автор(S, X).
?- томов(S, 2), название(S, N), автор(S, A), издательство(S, I).
?- автор(S, 'Пушкин А.С.'), год(S, G), G < 1990,
название(S, N).
?- город(S, G), G =\= 'Москва', издательство(S, I).
5. Интерпретация операций реляционной алгебры
Рассмотрим возможности реализации операций реляционной алгебры средствами Пролога.
Как известно, основными операциями реляционной алгебры яв-
ляются: проекция, соединение, пересечение и объединение отноше-
ний. Ограничимся операцией проекции - выделением части атрибутов отношения с исключением появляющихся при этом повторений.
116
Пример 8.4. Рассмотрим уже имеющееся отношение книга/10. Проекцией этого отношения на атрибуты город и издательство
будет новое отношение, которое мы назовем место/2. После исключения повторений в этом отношении будет 5 строк. Его будут представлять следующие факты Пролога:
место('Москва', 'Художественная литеpатуpа'). место('Москва', 'Русский язык').
место('Рига', 'Лиесма').
место('Москва', 'Московский pабочий'). место('Москва', 'Пpавда').
Рассмотрим определение предиката proj/2. Первым аргументом предиката служит спецификация исходного отношения в виде R/N, вторым аргументом – структура проекции в виде P/L, где P – имя нового отношения, а L – список номеров атрибутов исходного отношения, на которые оно проецируется.
Запрос на получение рассмотренной выше проекции следующий:
?- proj(книга/10, место/[9, 8]).
Определение предиката proj/2 таково (код 8.6):
Код 8.6
proj(R/N, P/L):-
functor(S, R, N), proj1(S, P/L).
proj1(S, P/L):- call(S), otobr(S, L, M),
Result =.. [P|M], assert_new_fact(Result), fail.
proj1(_, _):-!.
otobr(_, [], []):-!. otobr(S, [I|L], [X|M]):-
arg(I, S, X), otobr(S, L, M).
assert_new_fact(Fact):- call(Fact), !.
117
assert_new_fact(Fact):- assert(Fact), !.
Напомним, что использование предиката assert_new_fact/1 вместо встроенного предиката assert/1 обеспечивает устранение повторяющихся строк в результирующем отношении (одинаковых фактов Пролога).
В заключение сделаем следующее замечание. Интерпретация отношений реляционных баз данных в виде фактов Пролога, находящихся в оперативной памяти компьютера, позволяет работать,
естественно, лишь с небольшими, «игрушечными» базами. В некоторых реализациях Пролога (например, в системе Atity Prolog, опи-
санной в книге [3]) предусмотрены возможности взаимодействия Пролога с внешними носителями – с файлами, с виртуальной памятью, что позволяет говорить уже не об «игрушечных» примерах, а о реальных дедуктивных базах данных.
6.О статических и динамических предикатах Пролог системы LPA
ВПрологе LPA есть специфика – определяемые предикаты подразделяются на статические и динамические.
По умолчанию, при компиляции новых определений предикатов
спомощью встроенных предикатов consult/1 (перезагрузки) и compile/1 (компиляции) загружаемые и компилируемые предикаты считаются статическими.
С помощью встроенных предикатов assert/1 и retract/1 можно добавлять и удалять определения только динамических предикатов.
Определения статических предикатов могут быть изменены
лишь полным переопределением с помощью встроенных предика-
тов consult/1 и compile/1.
Полное удаление определений как статических, так и динамиче-
ских предикатов может быть произведено с помощью встроенного предиката abolish/1.
Определения статических предикатов можно удалить и другим
способом, объявив эти предикаты динамическими с помощью встроенного предиката dynamic/1.
Взаключение рассмотрим пример на использование динамических и статических предикатов.
118
Пример 8.5. Рассмотрим запись правил и фактов в БД Пролога. Задача такая: с помощью предиката task1 в базу данных запи-
сывается новое правило c( X, Y) :- a( X), b( Y)., позволяющее искать значения переменных X и Y по запросу: ?- c(X, Y).
Код 8.7
% Запись факта и правила в БД Пролога
:- dynamic( b/1).
a( Петя). a( Вася). b( Маша).
task1 :- assert( (c( X, Y) :- (a( X), b( Y)))). task2 :- assert( b( Даша)).
%Если несколько раз отрабатывать цель ?- task2.
%то в БД появятся несколько фактов b( Даша).
%Избежать этого можно, заменив assert/1 на assert_new_fact/1:
assert_new_fact( F) :- call( F), !. assert_new_fact( F) :- !, assert( F).
Комментарий. Для снятия блокировки при попытке записать новый факт b(Даша) в базу данных Пролога, когда в ней уже есть факт b(Маша), используется директива :- dynamic( b/1).
Следующая «фотография» консоли (код 8.8) достаточно красно-
речиво описывает результаты экспериментов с данной программой.
Код 8.8
| ?- task1. yes
| ?- bagof((X,Y), c(X,Y), L). X = _ ,
Y = _ ,
L = [(Петя,Маша),(Вася,Маша)]
| ?- task2. yes
| ?- bagof((X,Y), c(X,Y), L).
119
X = _ ,
Y = _ ,
L = [(Петя,Маша),(Петя,Даша),(Вася,Маша),(Вася,Даша)]
| ?- assert_new_fact(b(Глаша)). yes
| ?- bagof((X,Y), c(X,Y), L). X = _ ,
Y = _ ,
L = [(Петя,Маша),(Петя,Даша),(Петя,Глаша),(Вася,Маша), (Вася,Даша), (Вася,Глаша)]
| ?- assert_new_fact(b(Даша)). yes
| ?- bagof((X,Y), c(X,Y), L). X = _ ,
Y = _ ,
L = [(Петя,Маша),(Петя,Даша),(Петя,Глаша),(Вася,Маша), (Вася,Даша),(Вася,Глаша)]
| ?- task2. yes
| ?- bagof((X,Y), c(X,Y), L). X = _ ,
Y = _ ,
L = [(Петя,Маша),(Петя,Даша),(Петя,Глаша),(Петя,Даша),
(Вася,Маша),(Вася,Даша),(Вася,Глаша),(Вася,Даша)]
| ?-
Отметим, что попытка записать в базу данных новый факт с помощью цели ?- assert_new_fact(a(Дима)). потерпела бы фиаско, так как предикат a/1 в отличие от предиката b/1 является не динамическим, а статическим.
120