Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Функциональное и логическое программирование. Практикум
.pdf
domains
<описание доменов>
database
<описание предикатов динамической базы данных>
predicates
<описание предикатов>
goal
<предложение, описывающее внутреннюю цель программы>
/*В конце предложения всегда ставится точка*/
clauses
<факты и правила>
Программа может не содержать всех вышеперечисленных разделов.
Комментарии в программе обрамляются символами /* и */, как это показано
при описании структуры программы.
2.3.1 Описание доменов и предикатов, объекты данных
Раздел domains описывает новые типы данных. Имя домена должно
начинаться с маленькой буквы. Турбо–Пролог имеет 6 стандартных доменов
(типов данных), которые описывать не нужно:
символы (char). При использовании данных такого домена каждый символ
заключается в апострофы;
целые числа (integer). Диапазон значений данных от -32768 до 32767;
действительные числа (real). Диапазон значений данных от 10
-307
до 10
308
;
строки (string). Данные такого домена - последовательность символов (не
более 250), заключенная в кавычки или последовательность букв, цифр и
подчерков, начинающаяся со строчной буквы;
символические имена (symbol). Данные такого домена совпадают с
данными string. Отличие будет заключаться в способе хранения данных.
Данные типа symbol запоминаются в специальной таблице символов,
которая размещается в оперативной памяти и, поэтому, обеспечивает более
быстрый поиск. Однако, для построения такой таблицы требуется
дополнительное время;
файлы (file). Значением данных этого домена может быть любое допустимое
имя файла.
Стандартные домены не описываются. Домены, объявленные
пользователем, делают предикаты нагляднее, т.к. поясняют смысл предикатов.
Описание n-местного предиката имеет вид:
<имя предиката>(<домен1>,<домен2>,..,<доменn>), где
домен1, домен2,..,доменn – имена доменов (стандартных или определенных
программистом) для объектов описанного отношения.
Пример 1. Рассмотрим два способа описания одного и того же предиката –
наличие в библиотеке определенной книги.
1-ый способ (без использования доменов):
51

predicates
книга (string,string,integer,string)
2-ой способ (с использованием доменов):
domains
автор, заголовок, издательство = string
год_издания = integer
predicates
книга(автор,заголовок,год_издания,издательство)
Очевидно, что при наличии описанных доменов, назначение объектов
предиката становится более понятным.
В соответствии с теорией предикатов первого порядка единственной
структурой данных в логических программах являются термы.
Определение термов индуктивно. Термом может быть либо простой объект
(константа, переменная, список) либо сложным объектом (структурой).
Рисунок 12 - Данные в Прологе
Составной терм содержит функтор и последовательность из одного или
более аргументов, являющихся термами:
<имя структуры (функтор)>( <терм1>,<терм2>,…,<термn>)
Геометрически все структуры можно изображать в виде деревьев: корнем
дерева является функтор, а ветви дерева – компоненты функтора. Если
компонентой является структура, то ей соответствует поддерево в дереве всей
структуры. Функтор задается своим функциональным именем и арностью
(местностью или количеством аргументов). Синтаксически термы выглядят как
предикаты, поэтому говорят о единообразии программ и данных в Прологе, т.е.
предикат является так же термом.
В Турбо-Прологе допускается использование одного и того же
предикатного имени с разным количеством аргументов, поскольку каждый
функтор определяется своим именем и арностью. При этом следует иметь в
виду, что описания этих предикатов и соответствующие им правила должны
находиться рядом в разделах predicates и clauses.
52

Пример 2: Опишем предикат сумма, находящий сумму двух, трех или
четырех целых чисел.
domains
слагаемое,сумма=integer
predicates
сумма(слагаемое,слагаемое,сумма)
сумма(слагаемое,слагаемое,слагаемое,сумма)
сумма(слагаемое,слагаемое,слагаемое,слагаемое,сумма)
clauses
сумма(X1,X2,S):-S=X1+X2.
сумма(X1,X2,X3,S):-S=X1+X2+X3.
сумма(X1,X2,X3,X4,S):-S=X1+X2+X3+X4.
При этом можно в качестве цели указать как сумма(1,2,S), так и
сумма(1,2,3,S), так и сумма(1,2,3,4,S).
В разделе domains так же можно описывать структуры.
Пример 3: Пусть у Васи в сумке лежит книга И.Братко
“Программирование на языке ПРОЛОГ для искусственного интеллекта”. Этот
факт можно описать следующей программой с использованием структуры в
разделе domains.
domains
книга=книга(автор, заголовок)
где,автор,заголовок=symbol
predicates
находится(где,книга)
clauses
находится(сумка,книга(“И.Братко”,”Программирование на языке
ПРОЛОГ для искусственного интеллекта ”).
Пусть в сумке у Васи лежит так же синяя ручка. Тогда можно ввести
альтернативный домен, вместо того, чтобы описывать новый предикат.
domains
вещь=книга(автор,заголовок);
ручка(цвет)
где,автор,заголовок,цвет=symbol
predicates
находится(где,вещь)
clauses
находится(сумка,книга(“И.Братко”,”Программирование на языке
ПРОЛОГ для искусственного интеллекта ”).
находится(сумка, ручка(синий)).
53

2.3.2 Ввод-вывод в Турбо-Прологе
В Турбо-Прологе имеются следующие встроенные предикаты для ввода c
текущего устройства чтения (по умолчанию с клавиатуры) различных типов
данных:
readchar(<переменная>) – ввод символа (при вводе с клавиатуры ввод идет
без отображения на экране вводимого символа);
readint(<переменная>) – ввод целого числа;
readreal(<переменная>) – ввод вещественного числа;
readln(<переменная>) – ввод строки или символического имени;
readterm(<домен>,<переменная>) - ввод структуры указанного домена.
Для вывода на текущее устройство вывода (по умолчанию на экран)
используются предикаты:
write – вывод констант (должны быть заключены в кавычки) и значений
переменных стандартных доменных . Количество аргументов произвольно;
nl – перевод строки.
Для очистки экрана используется предикат без параметров: clearwindow.
2.3.3 Внутренние и внешние цели
Мы уже видели, что цель можно задавать во время диалога. При этом, если
в цели присутствовали переменные, то Пролог-система находит все возможные
значения этой переменной. После достижения внешней цели выполнение
программы продолжается (можно задавать новую цель). При этом конечно,
целевая формулировка не должна быть очень длинной.
В Турбо-Прологе имеется возможность описать внутреннюю цель в
разделе goal. После достижения внутренней цели выполнение программы
прекращается. Если во внутренней цели имеется переменная, то находится
только одно возможное значение этой переменной (первое в соответствии с
алгоритмом поиска). Значения этой переменной выводится на экран с помощью
предиката write. Для получения всех наборов допустимых решений необходимо
включение в правила специальных средств управлений поиском решений.
Внутренняя цель может содержать достаточно длинную формулировку.
Предложение внешней цели должно заканчиваться точкой.
Пример: Рассмотрим дерево семейных отношений из примера 1 п.2.1.
Зададим внутреннюю цель для ответа на вопрос: Кто родители Боба? В
программе следует описать раздел goal.
goal
clearwindow,
родитель(X,боб),
write(X,”-родитель боба”),
nl.
Можно описать то же самое по-другому, с использованием предиката без
аргументов и соответствующего правила:
54

predicates родитель_боба
goal
родитель_боба.
clauses
родитель_боба:-
clearwindow,
родитель(X,боб),
write(X,”-родитель боба”),
nl.
Заметим, что внутренняя цель найдет только одного родителя в
соответствии с последовательностью описанных фактов отношения родитель,
а именно Пам. Для поиска всех родителей следует применить специальные
средства поиска решений, о которых речь пойдет в дальнейшем.
2.4 Рекурсия
Рекурсия в Прологе может быть алгоритмическая и по данным. Основное
отличие от Лиспа заключается в том, что возвращаемое значение должно
находиться в аргументе предиката. Т.е., в отличие от Лиспа, где использовались
функции, в Прологе используются процедуры. При этом следует помнить, что
передача параметров осуществляется по значению.
Пример 1: Напишем программу, которая 5 раз выводит на экран строку
**********.
predicates
вывод_строки(integer)
goal
clearwindow,
вывод_строки(5).
clauses
вывод_строки(N):-N>0,
write(“**********”),nl,
N1=N–1,
вывод_строки(N1).
В данной программе недостатком является то, что цель не достигнута.
Если бы в разделе goal за предикатом вывод_строки следовало продолжение
предложения цели, то это продолжение не стало бы выполняться, не смотря на
то, что строка напечаталась бы 5 раз. Это связано с тем, что предикат
вывод_строки неудачно завершился при X=0 (не нашлось подходящего факта
или правила), т.е. не было правила выхода из рекурсии.
Изменим раздел clauses. Добавим правило остановки рекурсии для
успешного завершения предиката вывод_строки.
clauses
вывод_строки(0).
вывод_строки(N):-N > 0,
55

write(“**********”),nl,
N1=N–1,
вывод_строки(N1).
Пример 2: Напишем программу, выводит на экран целые числа от 5 до 15
в одну строку.
predicates
вывод_чисел(integer)
goal
clearwindow,
write(“Числа от 5 до 15”),nl,
вывод_чисел(5),nl,nl,
write(“Вывод закончен”),nl.
clauses
вывод_чисел(16).
вывод_чисел(N):-write(N, “ “),
N1=N+1,
вывод_чисел(N1).
Пример 3: Напишем программу вычисления n!, где n вводится с
клавиатуры.
domains
число,результат=integer
predicates
факториал(число,результат)
goal
clearwindow,
write(“Введите число для вычисления факториала ”),nl,
readint(N),
факториал(N,P),
write(N,“!=”,P),nl.
clauses
факториал(0,1).
факториал(N,P):-N>0,
N1=N-1,
факториал(N1,P1),
P=P1*N.
Пример 4: Напишем программу размена денежной суммы самыми
«крупными» монетами, если имеется ограниченное количество монет
достоинством 1, 2 и 5 рублей. Количество наличных разменных монет вводится
с клавиатуры.
domains
кол_1,кол_2,кол_5,итого_1,итого_2,итого_5,сумма=integer
predicates
размен(сумма,кол_1,кол_2,кол_5,итого_1,итого_2,итого_5)
56

мин(integer,integer,integer)
goal
clearwindow,
write(“Введите сумму для размена ”), readint(S),
write(“Введите количество наличных монет для размена”),nl,
write(“По 1 рублю - ”), readint(K1),
write(“По 2 рубля - ”), readint(K2),
write(“По 5 рублей - ”), readint(K3),
размен (S,K1,K2,K3,0,0,0).
clauses
размен(0,_,_,_,N1,N2,N3):-S=5*N3+2*N2+N1,
write(S,”=5*,N3,”+2*”,N2,”+”,N1).
размен(S,K1,K2,K3,N1,N2,N3):-S>=5,NN=S div 5,K3>0,
мин(NN,K3,MIN),S1=S-5*MIN,
L3=K3-MIN,M3=N3+MIN,
размен(S1,K1,K2,L3,N1,N2,M3).
размен(S,K1,K2,K3,N1,N2,N3):-S>=2,NN=S div 2,K2>0,
min(NN,K2,MIN),S1=S-2*MIN,
L2=K2-MIN,M2=N2+MIN,
размен(S1,K1,L2,K3,N1,M2,N3).
размен(S,K1,K2,K3,N1,N2,N3):-S>=1,K1>0,
min(S,K1,MIN),S1=S-MIN,
L1=K1-MIN,M1=N1+MIN,
размен(S1,L1,K2,K3,M1,N2,N3).
размен(_,_,_,_,_,_,_):-write(“Разменять не удалось”),nl.
мин(X,Y,X):-X<Y.
мин(_,Y,Y).
2.5 Семантика Пролога
2.5.1 Порядок предложений и целей
Программу на Прологе можно понимать по-разному: с декларативной и
процедурной точки зрения. Декларативная семантика касается только
отношений, описанных в программе, и определяет, что является ли
поставленная цель достижимой и если да, то определяются значения
переменных при которых эта цель достижима. Процедурная семантика
определяет, как должен быть получен результат, т.е. как Пролог-система
отвечает на вопросы. Для правила вида P:Q,R. декларативная семантика
определяет, что из истинности Q и R следует истинность P, а процедурная
семантика определяет, что для решения P следует сначала решить Q, а потом R
(важен порядок обработки целей).
Если рассмотреть верное с декларативной точки зрения правило P:P, то
можно заметить, что с процедурной точки зрения оно бесполезно, более того,
такое правило приводит к бесконечному циклу.
57

Рассмотрим насколько порядок предложений и целей, который не
затрагивает декларативную семантику, может изменить процедурную
семантику.
Пример: Рассмотрим определение предиката предок из примера 3 п.2.1:
предок(X,Y):–родитель(X,Y).
предок(X,Y):–родитель(X,Z),предок(Z,Y).
Попробуем поменять порядок следования правил и подцелей в правиле.
Рассмотрим еще три варианта этого отношения предок1, предок2, предок3.
1. предок1(X,Y):–родитель(X,Z),предок1(Z,Y).
предок1(X,Y):–родитель(X,Y).
2. предок2(X,Y):–родитель(X,Y).
предок2(X,Y):– предок2(Z,Y), родитель(X,Z).
3. предок3(X,Y):– предок3(Z,Y), родитель(X,Z).
предок3(X,Y):–родитель(X,Y).
При ответе на вопрос предок(том, боб) получим ответ «да» в результате
применения первого правила.
При ответе на вопрос предок1(том, боб) получим ответ «да» в результате
применения второго правила, но прежде, чем применить это правило Прологсистема будет пытаться применить первое правило для Боба и всех его
потомков. Решение будет найдено, но его поиск будет идти очень долго.
При ответе на вопрос предок2(том, боб) получим сразу ответ «да» в
результате применения первого правила. Но заметим, что этот предикат не
всегда будет давать верный ответ. Например, на вопрос предок2(лиз, боб) не
будет получено ответа, т.к. на первой подцели второго правила, Прологсистема уйдет в бесконечную рекурсию.
Ответ на вопрос предок3(том, боб) не будет получен, т.к. на первой
подцели первого правила, Пролог-система уйдет в бесконечную рекурсию.
Таким образом, правильные с декларативной точки зрения программы
могут работать неправильно. При составлении правил следует
руководствоваться следующим:
более простое правило следует ставить на первое место;
по возможности избегать левой рекурсии.
2.5.2 Пример декларативного создания программы
Задача об обезьяне и банане. Возле двери комнаты стоит обезьяна. В
середине комнаты к потолку подвешен банан. Обезьяна голодна и хочет съесть
банан, но не может до него дотянуться, находясь на полу. Около окна этой
комнаты находиться ящик, которым обезьяна может воспользоваться. Обезьяна
может предпринимать следующие действия: ходить по полу, залазить на ящик,
двигать ящик (если обезьяна находится возле ящика), схватить банан (если
обезьяна находится на ящике под бананом). Может ли обезьяна добраться до
банана?
58

Будем считать, что мир обезьяны всегда находится в некотором состоянии,
которое может изменяться со временем. Состояние обезьяньего мира
определяется четырьмя компонентами: горизонтальная позиция обезьяны,
вертикальная позиция обезьяны (на ящике или на полу), позиция ящика,
наличие у обезьяны банана (есть или нет). Объединим эти компоненты в
структуру функтором состояние(symbol, symbol, symbol, symbol).
Задачу можно рассматривать как игру для одного игрока – обезьяны.
Формализуем правила этой игры. Начальное состояние игры:
состояние(удвери, наполу, уокна, нет), цель игры: состояние(_,_,_, да).
Определим возможные ходы обезьяны: перейти в другое место, подвинуть
ящик, залезть на ящик, схватить банан. Не всякий ход допустим для каждого
состояния. Например, ход «схватить банан» допустим только для состояния,
когда обезьяна стоит на ящике в середине комнаты и еще не имеет банана, т.е.
состояние(середина, наящике, середина, нет). В результате хода система
переходит из одного состояния в другое. Для отражения таких переходов
определим предикат ход(состояние, действие, состояние).
domains
состояние=состояние(symbol, symbol, symbol, symbol)
действие=symbol;
подвинуть(symbol, symbol);
перейти(symbol, symbol)
predicates
ход(состояние, действие, состояние)
может_достать(состояние)
goal
может_достать(состояние(удвери, наполу, уокна, нет)).
clauses
ход(состояние(середина, наящике, середина, нет),
схватить,
состояние(середина, наящике, середина,да)).
ход(состояние(P, наполу, P, H),
залезть,
состояние(P, наящике, P, H)).
ход(состояние(P1, наполу, P1, H),
подвинуть(P1, P2),
состояние(P2, наполу, P2, H)).
ход(состояние(P1, наполу, P, H),
перейти (P1, P2),
состояние(P2, наполу, P, H)).
может_достать(состояние(_,_,_,да)).
может_достать(S1):-ход(S1, H, S2),
может_достать(S2).
59

Рассмотрим, как для такой декларативной программы Пролог-система
будет искать решение. Для того чтобы ответить на вопрос, Пролог-системе
пришлось сделать лишь один возврат. Причина такой эффективности –
правильно выбранный порядок следования предложений, описывающих ходы.
Однако возможен и другой порядок, когда обезьяна будет ходить туда-сюда, не
касаясь ящика, или бесцельно двигать ящик в разные стороны. Порядок
предложений и целей важен в программе.
Рисунок 13 - Поиск решений в задаче «Обезьяна и банан»
2.6 Внелогические предикаты управления поиском решений
В процессе достижения цели Пролог-система осуществляет
автоматический перебор вариантов, делая возврат при неуспехе какого-либо из
них. Такой перебор является полезным программным механизмом, поскольку
он освобождает пользователя от необходимости программировать такой
перебор. С другой стороны, неограниченный перебор может стать источником
60
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
